mirror of https://github.com/rails/rails
Ensure `response.parsed_body` support for pattern matching
Both `Nokogiri` and `Minitest` have merged the PRs mentioned to integrate support for Ruby's Pattern matching (https://github.com/sparklemotion/nokogiri/pull/2523 and https://github.com/minitest/minitest/pull/936, respectively). This commit adds coverage for those new assertions, and incorporates examples into the documentation for the `response.parsed_body` method. In order to incorporate pattern-matching support for JSON responses, this commit changes the response parser to call `JSON.parse` with [object_class: ActiveSupport::HashWithIndifferentAccess][object_class], since String instances for `Hash` keys are incompatible with Ruby's syntactically pattern matching. For example: ```ruby irb(main):001:0> json = {"key" => "value"} => {"key"=>"value"} irb(main):002:0> json in {key: /value/} => false irb(main):001:0> json = {"key" => "value"} => {"key"=>"value"} irb(main):002:0> json in {"key" => /value/} .../3.2.0/lib/ruby/gems/3.2.0/gems/irb-1.7.4/lib/irb/workspace.rb:113:in `eval': (irb):2: syntax error, unexpected terminator, expecting literal content or tSTRING_DBEG or tSTRING_DVAR or tLABEL_END (SyntaxError) json in {"key" => /value/} ^ .../ruby/3.2.0/lib/ruby/gems/3.2.0/gems/irb-1.7.4/exe/irb:9:in `<top (required)>' .../ruby/3.2.0/bin/irb:25:in `load' .../ruby/3.2.0/bin/irb:25:in `<main>' ``` When the Hash maps String keys to Symbol keys, it's able to be pattern matched: ```ruby irb(main):005:0> json = {"key" => "value"}.with_indifferent_access => {"key"=>"value"} irb(main):006:0> json in {key: /value/} => true ``` [object_class]: https://docs.ruby-lang.org/en/3.2/JSON.html#module-JSON-label-Parsing+Options
This commit is contained in:
parent
ed5af00459
commit
0f4ab82082
|
@ -1,3 +1,11 @@
|
|||
* Parse JSON `response.parsed_body` with `ActiveSupport::HashWithIndifferentAccess`
|
||||
|
||||
Integrate with Minitest's new `assert_pattern` by parsing the JSON contents
|
||||
of `response.parsed_body` with `ActiveSupport::HashWithIndifferentAccess`, so
|
||||
that it's pattern-matching compatible.
|
||||
|
||||
*Sean Doyle*
|
||||
|
||||
* Add support for Playwright as a driver for system tests.
|
||||
|
||||
*Yuki Nishijima*
|
||||
|
|
|
@ -53,6 +53,6 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
register_encoder :html, response_parser: -> body { Rails::Dom::Testing.html_document.parse(body) }
|
||||
register_encoder :json, response_parser: -> body { JSON.parse(body) }
|
||||
register_encoder :json, response_parser: -> body { JSON.parse(body, object_class: ActiveSupport::HashWithIndifferentAccess) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,15 +23,29 @@ module ActionDispatch
|
|||
# response.parsed_body.class # => Nokogiri::HTML5::Document
|
||||
# response.parsed_body.to_html # => "<!DOCTYPE html>\n<html>\n..."
|
||||
#
|
||||
# assert_pattern { response.parsed_body.at("main") => { content: "Hello, world" } }
|
||||
#
|
||||
# response.parsed_body.at("main") => {name:, content:}
|
||||
# assert_equal "main", name
|
||||
# assert_equal "Some main content", content
|
||||
#
|
||||
# get "/posts.json"
|
||||
# response.content_type # => "application/json; charset=utf-8"
|
||||
# response.parsed_body.class # => Array
|
||||
# response.parsed_body # => [{"id"=>42, "title"=>"Title"},...
|
||||
#
|
||||
# assert_pattern { response.parsed_body => [{ id: 42 }] }
|
||||
#
|
||||
# get "/posts/42.json"
|
||||
# response.content_type # => "application/json; charset=utf-8"
|
||||
# response.parsed_body.class # => Hash
|
||||
# response.parsed_body.class # => ActiveSupport::HashWithIndifferentAccess
|
||||
# response.parsed_body # => {"id"=>42, "title"=>"Title"}
|
||||
#
|
||||
# assert_pattern { response.parsed_body => [{ title: /title/i }] }
|
||||
#
|
||||
# response.parsed_body => {id:, title:}
|
||||
# assert_equal 42, id
|
||||
# assert_equal "Title", title
|
||||
def parsed_body
|
||||
@parsed_body ||= response_parser.call(body)
|
||||
end
|
||||
|
|
|
@ -25,6 +25,7 @@ class TestResponseTest < ActiveSupport::TestCase
|
|||
assert_equal response.body, response.parsed_body
|
||||
|
||||
response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '{ "foo": "fighters" }')
|
||||
assert_kind_of ActiveSupport::HashWithIndifferentAccess, response.parsed_body
|
||||
assert_equal({ "foo" => "fighters" }, response.parsed_body)
|
||||
|
||||
response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "text/html" }, <<~HTML)
|
||||
|
@ -38,4 +39,10 @@ class TestResponseTest < ActiveSupport::TestCase
|
|||
assert_kind_of(Nokogiri::XML::Document, response.parsed_body)
|
||||
assert_equal(response.parsed_body.at_xpath("/html/body/div").text, "Content")
|
||||
end
|
||||
|
||||
if RUBY_VERSION >= "3.1"
|
||||
require_relative "./test_response_test/pattern_matching_test_cases"
|
||||
|
||||
include PatternMatchingTestCases
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module TestResponseTest::PatternMatchingTestCases
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
test "JSON response Hash pattern matching" do
|
||||
response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '{ "foo": "fighters" }')
|
||||
|
||||
# rubocop:disable Lint/Syntax
|
||||
assert_pattern { response.parsed_body => { foo: /fighter/ } }
|
||||
# rubocop:enable Lint/Syntax
|
||||
end
|
||||
|
||||
test "JSON response Array pattern matching" do
|
||||
response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '[{ "foo": "fighters" }, { "nir": "vana" }]')
|
||||
# rubocop:disable Lint/Syntax
|
||||
assert_pattern { response.parsed_body => [{ foo: /fighter/ }, { nir: /vana/ }] }
|
||||
# rubocop:enable Lint/Syntax
|
||||
end
|
||||
|
||||
test "HTML response pattern matching" do
|
||||
response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "text/html" }, <<~HTML)
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<main><h1>Some main content</h1></main>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
html = response.parsed_body
|
||||
|
||||
# rubocop:disable Lint/Syntax
|
||||
html.at("main") => {name:, content:}
|
||||
# rubocop:enable Lint/Syntax
|
||||
|
||||
assert_equal "main", name
|
||||
assert_equal "Some main content", content
|
||||
|
||||
# rubocop:disable Lint/Syntax
|
||||
assert_pattern { html.at("main") => { content: "Some main content" } }
|
||||
assert_pattern { html.at("main") => { content: /content/ } }
|
||||
assert_pattern { html.at("main") => { children: [{ name: "h1", content: /content/ }] } }
|
||||
# rubocop:enable Lint/Syntax
|
||||
end
|
||||
end
|
||||
end
|
|
@ -138,11 +138,11 @@ class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::Integrati
|
|||
test "creating new direct upload" do
|
||||
checksum = OpenSSL::Digest::MD5.base64digest("Hello")
|
||||
metadata = {
|
||||
"foo": "bar",
|
||||
"my_key_1": "my_value_1",
|
||||
"my_key_2": "my_value_2",
|
||||
"platform": "my_platform",
|
||||
"library_ID": "12345"
|
||||
"foo" => "bar",
|
||||
"my_key_1" => "my_value_1",
|
||||
"my_key_2" => "my_value_2",
|
||||
"platform" => "my_platform",
|
||||
"library_ID" => "12345"
|
||||
}
|
||||
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
|
@ -153,7 +153,7 @@ class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::Integrati
|
|||
assert_equal "hello.txt", details["filename"]
|
||||
assert_equal 6, details["byte_size"]
|
||||
assert_equal checksum, details["checksum"]
|
||||
assert_equal metadata, details["metadata"].deep_transform_keys(&:to_sym)
|
||||
assert_equal metadata, details["metadata"]
|
||||
assert_equal "text/plain", details["content_type"]
|
||||
assert_match(/rails\/active_storage\/disk/, details["direct_upload"]["url"])
|
||||
assert_equal({ "Content-Type" => "text/plain" }, details["direct_upload"]["headers"])
|
||||
|
|
Loading…
Reference in New Issue