Module: RSpec::Signal::Symptoms::HttpStatus
- Defined in:
- lib/rspec/signal/symptoms/http_status.rb
Overview
An HTTP status the application returned but the spec did not expect.
Clustered on the actual status, because that is the fact with a shared cause: eight specs that each wanted something different and all got 404 are eight symptoms of one broken route. The expected side survives only as this failure's detail line, so a cluster can still show that some examples wanted 200 and others wanted a redirect.
Constant Summary collapse
- CODE =
One side of a status mismatch:
404,:not_found,:ok (200). /:?[\w-]+(?: \(\d{3}\))?/- PATTERNS =
[ # rspec-rails `have_http_status(200)` / `have_http_status(:ok)` /expected the response to have status code (?<expected>#{CODE}) *,? *but it was (?<actual>#{CODE})/i, # rspec-rails `have_http_status(:success)` / `:redirect` / `:error` / `:missing` /expected the response to have an? (?<expected>\w+) status code \([^)]*\) *,? *but it was (?<actual>#{CODE})/i, # rubocop:disable Layout/LineLength # Rails' own `assert_response` /expected response to be a <(?<expected>[^>]+)>, *but was a <(?<actual>[^>]+)>/i, # The shorthand several house matchers use /expected(?: the)? response status (?:to be )?(?<expected>:?[\w-]+) *,? *(?:but )?got:? *(?<actual>:?[\w-]+)/i ].freeze
- NAMES =
Enough of the standard set to name the common ones. An unrecognised code still clusters, it just does not get a friendly name.
{ "200" => "OK", "201" => "Created", "204" => "No Content", "301" => "Moved Permanently", "302" => "Found", "304" => "Not Modified", "400" => "Bad Request", "401" => "Unauthorized", "403" => "Forbidden", "404" => "Not Found", "406" => "Not Acceptable", "409" => "Conflict", "422" => "Unprocessable Entity", "429" => "Too Many Requests", "500" => "Internal Server Error", "502" => "Bad Gateway", "503" => "Service Unavailable" }.freeze
- CODES =
{ "ok" => "200", "created" => "201", "no_content" => "204", "moved_permanently" => "301", "found" => "302", "not_modified" => "304", "bad_request" => "400", "unauthorized" => "401", "forbidden" => "403", "not_found" => "404", "not_acceptable" => "406", "conflict" => "409", "unprocessable_entity" => "422", "unprocessable_content" => "422", "too_many_requests" => "429", "internal_server_error" => "500", "bad_gateway" => "502", "service_unavailable" => "503" }.freeze
- GENERIC_EXPECTATION =
/expected:\s*(?<expected>\d{3})\s+got:\s*(?<actual>\d{3})/i- STATUS_EXPRESSION =
/(?:response\s*\.\s*status|response\s*\.\s*status_code|response\s*\[\s*:status\s*\])/i
Class Method Summary collapse
- .call(failure, text) ⇒ Object
-
.code(raw) ⇒ Object
The numeric code, whether it arrived as a number, a symbol, or both.
-
.generic_expectation(failure, text) ⇒ Object
RSpec's generic equality matcher does not mention HTTP in its expected/got lines.
- .label(actual) ⇒ Object
-
.token(raw) ⇒ Object
How to word one side of the mismatch: a code where there is one, and otherwise the word the matcher used --
redirect,success.
Class Method Details
.call(failure, text) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/rspec/signal/symptoms/http_status.rb', line 51 def call(failure, text) match = PATTERNS.filter_map { |pattern| pattern.match(text) }.first match ||= generic_expectation(failure, text) return nil unless match actual = code(match[:actual]) return nil unless actual Symptom.new(kind: :http_status, key: "http-status:#{actual}", label: label(actual), detail: "expected #{token(match[:expected])}, got #{actual}") end |
.code(raw) ⇒ Object
The numeric code, whether it arrived as a number, a symbol, or both.
79 80 81 82 83 84 |
# File 'lib/rspec/signal/symptoms/http_status.rb', line 79 def code(raw) text = raw.to_s return ::Regexp.last_match(1) if text =~ /(\d{3})/ CODES[text.delete_prefix(":").downcase] end |
.generic_expectation(failure, text) ⇒ Object
RSpec's generic equality matcher does not mention HTTP in its expected/got lines. Only accept it when the captured failing expression explicitly reads a response status, avoiding arbitrary three-digit numeric comparisons.
67 68 69 70 71 |
# File 'lib/rspec/signal/symptoms/http_status.rb', line 67 def generic_expectation(failure, text) return nil unless STATUS_EXPRESSION.match?(failure..text.to_s) GENERIC_EXPECTATION.match(text) end |
.label(actual) ⇒ Object
73 74 75 76 |
# File 'lib/rspec/signal/symptoms/http_status.rb', line 73 def label(actual) name = NAMES[actual] "unexpected #{actual}#{" (#{name})" if name} responses" end |
.token(raw) ⇒ Object
How to word one side of the mismatch: a code where there is one, and
otherwise the word the matcher used -- redirect, success.
88 89 90 91 92 93 94 |
# File 'lib/rspec/signal/symptoms/http_status.rb', line 88 def token(raw) text = raw.to_s.strip return ::Regexp.last_match(1) if text =~ /(\d{3})/ = text.delete_prefix(":").downcase CODES[] || end |