mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge branch 'json-gem-tests'
This commit is contained in:
commit
794252d709
3 changed files with 186 additions and 102 deletions
66
activesupport/test/core_ext/object/json_gem_encoding_test.rb
Normal file
66
activesupport/test/core_ext/object/json_gem_encoding_test.rb
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
require 'abstract_unit'
|
||||||
|
require 'json'
|
||||||
|
require 'json/encoding_test_cases'
|
||||||
|
|
||||||
|
# These test cases were added to test that we do not interfere with json gem's
|
||||||
|
# output when the AS encoder is loaded, primarily for problems reported in
|
||||||
|
# #20775. They need to be executed in isolation to reproduce the scenario
|
||||||
|
# correctly, because other test cases might have already loaded additional
|
||||||
|
# dependencies.
|
||||||
|
|
||||||
|
# The AS::JSON encoder requires the BigDecimal core_ext, which, unfortunately,
|
||||||
|
# changes the BigDecimal#to_s output, and consequently the JSON gem output. So
|
||||||
|
# we need to require this unfront to ensure we don't get a false failure, but
|
||||||
|
# ideally we should just fix the BigDecimal core_ext to not change to_s without
|
||||||
|
# arguments.
|
||||||
|
require 'active_support/core_ext/big_decimal'
|
||||||
|
|
||||||
|
class JsonGemEncodingTest < ActiveSupport::TestCase
|
||||||
|
include ActiveSupport::Testing::Isolation
|
||||||
|
|
||||||
|
JSONTest::EncodingTestCases.constants.each_with_index do |name|
|
||||||
|
JSONTest::EncodingTestCases.const_get(name).each_with_index do |(subject, _), i|
|
||||||
|
test("#{name[0..-6].underscore} #{i}") do
|
||||||
|
assert_same_with_or_without_active_support(subject)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CustomToJson
|
||||||
|
def to_json(*)
|
||||||
|
'"custom"'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "custom to_json" do
|
||||||
|
assert_same_with_or_without_active_support(CustomToJson.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def require_or_skip(file)
|
||||||
|
require(file) || skip("'#{file}' was already loaded")
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_same_with_or_without_active_support(subject)
|
||||||
|
begin
|
||||||
|
expected = JSON.generate(subject, quirks_mode: true)
|
||||||
|
rescue JSON::GeneratorError => e
|
||||||
|
exception = e
|
||||||
|
end
|
||||||
|
|
||||||
|
require_or_skip 'active_support/core_ext/object/json'
|
||||||
|
|
||||||
|
if exception
|
||||||
|
assert_raises_with_message JSON::GeneratorError, e.message do
|
||||||
|
JSON.generate(subject, quirks_mode: true)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
assert_equal expected, JSON.generate(subject, quirks_mode: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_raises_with_message(exception_class, message, &block)
|
||||||
|
err = assert_raises(exception_class) { block.call }
|
||||||
|
assert_match message, err.message
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,122 +4,24 @@ require 'active_support/core_ext/string/inflections'
|
||||||
require 'active_support/json'
|
require 'active_support/json'
|
||||||
require 'active_support/time'
|
require 'active_support/time'
|
||||||
require 'time_zone_test_helpers'
|
require 'time_zone_test_helpers'
|
||||||
|
require 'json/encoding_test_cases'
|
||||||
|
|
||||||
class TestJSONEncoding < ActiveSupport::TestCase
|
class TestJSONEncoding < ActiveSupport::TestCase
|
||||||
include TimeZoneTestHelpers
|
include TimeZoneTestHelpers
|
||||||
|
|
||||||
class Foo
|
|
||||||
def initialize(a, b)
|
|
||||||
@a, @b = a, b
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Hashlike
|
|
||||||
def to_hash
|
|
||||||
{ :foo => "hello", :bar => "world" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Custom
|
|
||||||
def initialize(serialized)
|
|
||||||
@serialized = serialized
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(options = nil)
|
|
||||||
@serialized
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CustomWithOptions
|
|
||||||
attr_accessor :foo, :bar
|
|
||||||
|
|
||||||
def as_json(options={})
|
|
||||||
options[:only] = %w(foo bar)
|
|
||||||
super(options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class OptionsTest
|
|
||||||
def as_json(options = :default)
|
|
||||||
options
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class HashWithAsJson < Hash
|
|
||||||
attr_accessor :as_json_called
|
|
||||||
|
|
||||||
def initialize(*)
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(options={})
|
|
||||||
@as_json_called = true
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
TrueTests = [[ true, %(true) ]]
|
|
||||||
FalseTests = [[ false, %(false) ]]
|
|
||||||
NilTests = [[ nil, %(null) ]]
|
|
||||||
NumericTests = [[ 1, %(1) ],
|
|
||||||
[ 2.5, %(2.5) ],
|
|
||||||
[ 0.0/0.0, %(null) ],
|
|
||||||
[ 1.0/0.0, %(null) ],
|
|
||||||
[ -1.0/0.0, %(null) ],
|
|
||||||
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
|
|
||||||
[ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
|
|
||||||
|
|
||||||
StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
|
|
||||||
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
|
|
||||||
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
|
|
||||||
[ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
|
|
||||||
%("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
|
|
||||||
|
|
||||||
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
|
|
||||||
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
|
|
||||||
|
|
||||||
RangeTests = [[ 1..2, %("1..2")],
|
|
||||||
[ 1...2, %("1...2")],
|
|
||||||
[ 1.5..2.5, %("1.5..2.5")]]
|
|
||||||
|
|
||||||
SymbolTests = [[ :a, %("a") ],
|
|
||||||
[ :this, %("this") ],
|
|
||||||
[ :"a b", %("a b") ]]
|
|
||||||
|
|
||||||
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
|
|
||||||
HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
|
|
||||||
CustomTests = [[ Custom.new("custom"), '"custom"' ],
|
|
||||||
[ Custom.new(nil), 'null' ],
|
|
||||||
[ Custom.new(:a), '"a"' ],
|
|
||||||
[ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
|
|
||||||
[ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
|
|
||||||
[ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
|
|
||||||
[ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
|
|
||||||
|
|
||||||
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
|
|
||||||
|
|
||||||
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
|
|
||||||
TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
|
|
||||||
DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
|
|
||||||
|
|
||||||
StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
|
|
||||||
StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
|
|
||||||
StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
|
|
||||||
StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
|
|
||||||
|
|
||||||
def sorted_json(json)
|
def sorted_json(json)
|
||||||
return json unless json =~ /^\{.*\}$/
|
return json unless json =~ /^\{.*\}$/
|
||||||
'{' + json[1..-2].split(',').sort.join(',') + '}'
|
'{' + json[1..-2].split(',').sort.join(',') + '}'
|
||||||
end
|
end
|
||||||
|
|
||||||
constants.grep(/Tests$/).each do |class_tests|
|
JSONTest::EncodingTestCases.constants.each do |class_tests|
|
||||||
define_method("test_#{class_tests[0..-6].underscore}") do
|
define_method("test_#{class_tests[0..-6].underscore}") do
|
||||||
begin
|
begin
|
||||||
prev = ActiveSupport.use_standard_json_time_format
|
prev = ActiveSupport.use_standard_json_time_format
|
||||||
|
|
||||||
ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
|
ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
|
||||||
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
|
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
|
||||||
self.class.const_get(class_tests).each do |pair|
|
JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
|
||||||
assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
|
assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
|
@ -224,7 +126,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_hash_like_with_options
|
def test_hash_like_with_options
|
||||||
h = Hashlike.new
|
h = JSONTest::Hashlike.new
|
||||||
json = h.to_json :only => [:foo]
|
json = h.to_json :only => [:foo]
|
||||||
|
|
||||||
assert_equal({"foo"=>"hello"}, JSON.parse(json))
|
assert_equal({"foo"=>"hello"}, JSON.parse(json))
|
||||||
|
@ -345,6 +247,15 @@ class TestJSONEncoding < ActiveSupport::TestCase
|
||||||
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
|
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class CustomWithOptions
|
||||||
|
attr_accessor :foo, :bar
|
||||||
|
|
||||||
|
def as_json(options={})
|
||||||
|
options[:only] = %w(foo bar)
|
||||||
|
super(options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_hash_to_json_should_not_keep_options_around
|
def test_hash_to_json_should_not_keep_options_around
|
||||||
f = CustomWithOptions.new
|
f = CustomWithOptions.new
|
||||||
f.foo = "hello"
|
f.foo = "hello"
|
||||||
|
@ -365,6 +276,12 @@ class TestJSONEncoding < ActiveSupport::TestCase
|
||||||
{"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
|
{"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class OptionsTest
|
||||||
|
def as_json(options = :default)
|
||||||
|
options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_hash_as_json_without_options
|
def test_hash_as_json_without_options
|
||||||
json = { foo: OptionsTest.new }.as_json
|
json = { foo: OptionsTest.new }.as_json
|
||||||
assert_equal({"foo" => :default}, json)
|
assert_equal({"foo" => :default}, json)
|
||||||
|
@ -412,6 +329,19 @@ class TestJSONEncoding < ActiveSupport::TestCase
|
||||||
assert_equal false, false.as_json
|
assert_equal false, false.as_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class HashWithAsJson < Hash
|
||||||
|
attr_accessor :as_json_called
|
||||||
|
|
||||||
|
def initialize(*)
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_json(options={})
|
||||||
|
@as_json_called = true
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_json_gem_dump_by_passing_active_support_encoder
|
def test_json_gem_dump_by_passing_active_support_encoder
|
||||||
h = HashWithAsJson.new
|
h = HashWithAsJson.new
|
||||||
h[:foo] = "hello"
|
h[:foo] = "hello"
|
||||||
|
|
88
activesupport/test/json/encoding_test_cases.rb
Normal file
88
activesupport/test/json/encoding_test_cases.rb
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
require 'bigdecimal'
|
||||||
|
|
||||||
|
module JSONTest
|
||||||
|
class Foo
|
||||||
|
def initialize(a, b)
|
||||||
|
@a, @b = a, b
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Hashlike
|
||||||
|
def to_hash
|
||||||
|
{ :foo => "hello", :bar => "world" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Custom
|
||||||
|
def initialize(serialized)
|
||||||
|
@serialized = serialized
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_json(options = nil)
|
||||||
|
@serialized
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MyStruct < Struct.new(:name, :value)
|
||||||
|
def initialize(*)
|
||||||
|
@unused = "unused instance variable"
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module EncodingTestCases
|
||||||
|
TrueTests = [[ true, %(true) ]]
|
||||||
|
FalseTests = [[ false, %(false) ]]
|
||||||
|
NilTests = [[ nil, %(null) ]]
|
||||||
|
NumericTests = [[ 1, %(1) ],
|
||||||
|
[ 2.5, %(2.5) ],
|
||||||
|
[ 0.0/0.0, %(null) ],
|
||||||
|
[ 1.0/0.0, %(null) ],
|
||||||
|
[ -1.0/0.0, %(null) ],
|
||||||
|
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
|
||||||
|
[ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
|
||||||
|
|
||||||
|
StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
|
||||||
|
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
|
||||||
|
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
|
||||||
|
[ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
|
||||||
|
%("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
|
||||||
|
|
||||||
|
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
|
||||||
|
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
|
||||||
|
|
||||||
|
HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ],
|
||||||
|
[ {1 => 1, 2 => 'a', 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
|
||||||
|
|
||||||
|
RangeTests = [[ 1..2, %("1..2")],
|
||||||
|
[ 1...2, %("1...2")],
|
||||||
|
[ 1.5..2.5, %("1.5..2.5")]]
|
||||||
|
|
||||||
|
SymbolTests = [[ :a, %("a") ],
|
||||||
|
[ :this, %("this") ],
|
||||||
|
[ :"a b", %("a b") ]]
|
||||||
|
|
||||||
|
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
|
||||||
|
HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
|
||||||
|
StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
|
||||||
|
[ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
|
||||||
|
CustomTests = [[ Custom.new("custom"), '"custom"' ],
|
||||||
|
[ Custom.new(nil), 'null' ],
|
||||||
|
[ Custom.new(:a), '"a"' ],
|
||||||
|
[ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
|
||||||
|
[ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
|
||||||
|
[ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
|
||||||
|
[ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
|
||||||
|
|
||||||
|
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
|
||||||
|
|
||||||
|
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
|
||||||
|
TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
|
||||||
|
DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
|
||||||
|
|
||||||
|
StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
|
||||||
|
StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
|
||||||
|
StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
|
||||||
|
StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue