oj 3.9.1 → 3.10.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/oj/compat.c +5 -5
  4. data/ext/oj/custom.c +6 -2
  5. data/ext/oj/dump.c +10 -13
  6. data/ext/oj/dump_compat.c +8 -10
  7. data/ext/oj/dump_object.c +18 -11
  8. data/ext/oj/dump_strict.c +6 -5
  9. data/ext/oj/extconf.rb +5 -0
  10. data/ext/oj/mimic_json.c +15 -3
  11. data/ext/oj/object.c +2 -1
  12. data/ext/oj/oj.c +47 -28
  13. data/ext/oj/oj.h +4 -2
  14. data/ext/oj/parse.c +16 -3
  15. data/ext/oj/parse.h +1 -0
  16. data/ext/oj/rails.c +37 -4
  17. data/ext/oj/sparse.c +5 -0
  18. data/ext/oj/util.c +5 -5
  19. data/ext/oj/wab.c +9 -9
  20. data/lib/oj/version.rb +1 -1
  21. data/pages/Rails.md +60 -21
  22. data/test/activesupport5/abstract_unit.rb +45 -0
  23. data/test/activesupport5/decoding_test.rb +68 -60
  24. data/test/activesupport5/encoding_test.rb +111 -96
  25. data/test/activesupport5/encoding_test_cases.rb +33 -25
  26. data/test/activesupport5/test_helper.rb +43 -21
  27. data/test/activesupport5/time_zone_test_helpers.rb +18 -3
  28. data/test/activesupport6/abstract_unit.rb +44 -0
  29. data/test/activesupport6/decoding_test.rb +133 -0
  30. data/test/activesupport6/encoding_test.rb +507 -0
  31. data/test/activesupport6/encoding_test_cases.rb +98 -0
  32. data/test/activesupport6/test_common.rb +17 -0
  33. data/test/activesupport6/test_helper.rb +163 -0
  34. data/test/activesupport6/time_zone_test_helpers.rb +39 -0
  35. data/test/bar.rb +21 -11
  36. data/test/baz.rb +16 -0
  37. data/test/foo.rb +39 -8
  38. data/test/test_compat.rb +0 -7
  39. data/test/test_custom.rb +25 -6
  40. data/test/test_integer_range.rb +1 -2
  41. data/test/test_object.rb +12 -3
  42. data/test/test_rails.rb +26 -0
  43. data/test/test_strict.rb +24 -1
  44. data/test/test_various.rb +41 -62
  45. data/test/tests.rb +1 -0
  46. metadata +23 -3
@@ -1,6 +1,10 @@
1
- require 'bigdecimal'
2
- require 'date'
3
- require 'time'
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "date"
5
+ require "time"
6
+ require "pathname"
7
+ require "uri"
4
8
 
5
9
  module JSONTest
6
10
  class Foo
@@ -11,7 +15,7 @@ module JSONTest
11
15
 
12
16
  class Hashlike
13
17
  def to_hash
14
- { :foo => "hello", :bar => "world" }
18
+ { foo: "hello", bar: "world" }
15
19
  end
16
20
  end
17
21
 
@@ -25,7 +29,7 @@ module JSONTest
25
29
  end
26
30
  end
27
31
 
28
- class MyStruct < Struct.new(:name, :value)
32
+ MyStruct = Struct.new(:name, :value) do
29
33
  def initialize(*)
30
34
  @unused = "unused instance variable"
31
35
  super
@@ -38,23 +42,23 @@ module JSONTest
38
42
  NilTests = [[ nil, %(null) ]]
39
43
  NumericTests = [[ 1, %(1) ],
40
44
  [ 2.5, %(2.5) ],
41
- [ 0.0/0.0, %(null) ],
42
- [ 1.0/0.0, %(null) ],
43
- [ -1.0/0.0, %(null) ],
44
- [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
45
- [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
45
+ [ 0.0 / 0.0, %(null) ],
46
+ [ 1.0 / 0.0, %(null) ],
47
+ [ -1.0 / 0.0, %(null) ],
48
+ [ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ],
49
+ [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]]
46
50
 
47
- StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
51
+ StringTests = [[ "this is the <string>", %("this is the \\u003cstring\\u003e")],
48
52
  [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
49
- [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
53
+ [ "http://test.host/posts/1", %("http://test.host/posts/1")],
50
54
  [ "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",
51
55
  %("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") ]]
52
56
 
53
- ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
54
- [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
57
+ ArrayTests = [[ ["a", "b", "c"], %([\"a\",\"b\",\"c\"]) ],
58
+ [ [1, "a", :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
55
59
 
56
- HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ],
57
- [ {1 => 1, 2 => 'a', 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
60
+ HashTests = [[ { foo: "bar" }, %({\"foo\":\"bar\"}) ],
61
+ [ { 1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false }, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
58
62
 
59
63
  RangeTests = [[ 1..2, %("1..2")],
60
64
  [ 1...2, %("1...2")],
@@ -69,22 +73,26 @@ module JSONTest
69
73
  StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
70
74
  [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
71
75
  CustomTests = [[ Custom.new("custom"), '"custom"' ],
72
- [ Custom.new(nil), 'null' ],
76
+ [ Custom.new(nil), "null" ],
73
77
  [ Custom.new(:a), '"a"' ],
74
78
  [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
75
- [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
79
+ [ Custom.new(foo: "hello", bar: "world"), '{"bar":"world","foo":"hello"}' ],
76
80
  [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
77
81
  [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
78
82
 
79
83
  RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
80
84
 
81
- DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
82
- TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
83
- DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
85
+ URITests = [[ URI.parse("http://example.com"), %("http://example.com") ]]
86
+
87
+ PathnameTests = [[ Pathname.new("lib/index.rb"), %("lib/index.rb") ]]
88
+
89
+ DateTests = [[ Date.new(2005, 2, 1), %("2005/02/01") ]]
90
+ TimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
91
+ DateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
84
92
 
85
- StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
86
- StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
87
- StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
88
- StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
93
+ StandardDateTests = [[ Date.new(2005, 2, 1), %("2005-02-01") ]]
94
+ StandardTimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000Z") ]]
95
+ StandardDateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000+00:00") ]]
96
+ StandardStringTests = [[ "this is the <string>", %("this is the <string>")]]
89
97
  end
90
98
  end
@@ -1,25 +1,54 @@
1
- require 'active_support/testing/assertions'
2
- require 'active_support/testing/deprecation'
3
- require 'active_support/testing/declarative'
4
- require 'minitest/autorun'
1
+ # frozen_string_literal: true
2
+
3
+ gem "minitest" # make sure we get the gem, not stdlib
4
+ require "minitest"
5
+ require "active_support/testing/tagged_logging"
6
+ require "active_support/testing/setup_and_teardown"
7
+ require "active_support/testing/assertions"
8
+ require "active_support/testing/deprecation"
9
+ require "active_support/testing/declarative"
10
+ require "active_support/testing/isolation"
11
+ require "active_support/testing/constant_lookup"
12
+ require "active_support/testing/time_helpers"
13
+ require "active_support/testing/file_fixtures"
5
14
 
6
15
  module ActiveSupport
7
16
  class TestCase < ::Minitest::Test
8
- # Skips the current run on Rubinius using Minitest::Assertions#skip
9
- private def rubinius_skip(message = "")
10
- skip message if RUBY_ENGINE == "rbx"
11
- end
12
- # Skips the current run on JRuby using Minitest::Assertions#skip
13
- private def jruby_skip(message = "")
14
- skip message if defined?(JRUBY_VERSION)
15
- end
16
-
17
17
  Assertion = Minitest::Assertion
18
18
 
19
+ class << self
20
+ # Sets the order in which test cases are run.
21
+ #
22
+ # ActiveSupport::TestCase.test_order = :random # => :random
23
+ #
24
+ # Valid values are:
25
+ # * +:random+ (to run tests in random order)
26
+ # * +:parallel+ (to run tests in parallel)
27
+ # * +:sorted+ (to run tests alphabetically by method name)
28
+ # * +:alpha+ (equivalent to +:sorted+)
29
+ def test_order=(new_order)
30
+ ActiveSupport.test_order = new_order
31
+ end
32
+
33
+ # Returns the order in which test cases are run.
34
+ #
35
+ # ActiveSupport::TestCase.test_order # => :random
36
+ #
37
+ # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
38
+ # Defaults to +:random+.
39
+ def test_order
40
+ ActiveSupport.test_order ||= :random
41
+ end
42
+ end
43
+
19
44
  alias_method :method_name, :name
20
45
 
46
+ include ActiveSupport::Testing::TaggedLogging
47
+ prepend ActiveSupport::Testing::SetupAndTeardown
21
48
  include ActiveSupport::Testing::Assertions
22
49
  include ActiveSupport::Testing::Deprecation
50
+ include ActiveSupport::Testing::TimeHelpers
51
+ include ActiveSupport::Testing::FileFixtures
23
52
  extend ActiveSupport::Testing::Declarative
24
53
 
25
54
  # test/unit backwards compatibility methods
@@ -38,13 +67,6 @@ module ActiveSupport
38
67
  alias :assert_not_respond_to :refute_respond_to
39
68
  alias :assert_not_same :refute_same
40
69
 
41
- # Fails if the block raises an exception.
42
- #
43
- # assert_nothing_raised do
44
- # ...
45
- # end
46
- def assert_nothing_raised(*args)
47
- yield
48
- end
70
+ ActiveSupport.run_load_hooks(:active_support_test_case, self)
49
71
  end
50
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TimeZoneTestHelpers
2
4
  def with_tz_default(tz = nil)
3
5
  old_tz = Time.zone
@@ -7,11 +9,11 @@ module TimeZoneTestHelpers
7
9
  Time.zone = old_tz
8
10
  end
9
11
 
10
- def with_env_tz(new_tz = 'US/Eastern')
11
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
12
+ def with_env_tz(new_tz = "US/Eastern")
13
+ old_tz, ENV["TZ"] = ENV["TZ"], new_tz
12
14
  yield
13
15
  ensure
14
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
16
+ old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ")
15
17
  end
16
18
 
17
19
  def with_preserve_timezone(value)
@@ -21,4 +23,17 @@ module TimeZoneTestHelpers
21
23
  ensure
22
24
  ActiveSupport.to_time_preserves_timezone = old_preserve_tz
23
25
  end
26
+
27
+ def with_tz_mappings(mappings)
28
+ old_mappings = ActiveSupport::TimeZone::MAPPING.dup
29
+ ActiveSupport::TimeZone.clear
30
+ ActiveSupport::TimeZone::MAPPING.clear
31
+ ActiveSupport::TimeZone::MAPPING.merge!(mappings)
32
+
33
+ yield
34
+ ensure
35
+ ActiveSupport::TimeZone.clear
36
+ ActiveSupport::TimeZone::MAPPING.clear
37
+ ActiveSupport::TimeZone::MAPPING.merge!(old_mappings)
38
+ end
24
39
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ ORIG_ARGV = ARGV.dup
4
+
5
+ require "active_support/core_ext/kernel/reporting"
6
+
7
+ silence_warnings do
8
+ Encoding.default_internal = Encoding::UTF_8
9
+ Encoding.default_external = Encoding::UTF_8
10
+ end
11
+
12
+ require "active_support/testing/autorun"
13
+ require "active_support/testing/method_call_assertions"
14
+
15
+ ENV["NO_RELOAD"] = "1"
16
+ require "active_support"
17
+
18
+ Thread.abort_on_exception = true
19
+
20
+ # Show backtraces for deprecated behavior for quicker cleanup.
21
+ ActiveSupport::Deprecation.debug = true
22
+
23
+ # Default to old to_time behavior but allow running tests with new behavior
24
+ ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1"
25
+
26
+ # Disable available locale checks to avoid warnings running the test suite.
27
+ I18n.enforce_available_locales = false
28
+
29
+ class ActiveSupport::TestCase
30
+ include ActiveSupport::Testing::MethodCallAssertions
31
+
32
+ private
33
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
34
+ def rubinius_skip(message = "")
35
+ skip message if RUBY_ENGINE == "rbx"
36
+ end
37
+
38
+ # Skips the current run on JRuby using Minitest::Assertions#skip
39
+ def jruby_skip(message = "")
40
+ skip message if defined?(JRUBY_VERSION)
41
+ end
42
+ end
43
+
44
+ require_relative "test_common"
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "abstract_unit"
4
+ require "active_support/json"
5
+ require "active_support/time"
6
+ require_relative "time_zone_test_helpers"
7
+
8
+ require 'oj'
9
+
10
+ Oj::Rails.set_decoder()
11
+
12
+ class TestJSONDecoding < ActiveSupport::TestCase
13
+ include TimeZoneTestHelpers
14
+
15
+ # Added for testing if Oj is used.
16
+ test "oj is used as an encoder" do
17
+ assert_equal ActiveSupport.json_encoder, Oj::Rails::Encoder
18
+ end
19
+
20
+ class Foo
21
+ def self.json_create(object)
22
+ "Foo"
23
+ end
24
+ end
25
+
26
+ TESTS = {
27
+ %q({"returnTo":{"\/categories":"\/"}}) => { "returnTo" => { "/categories" => "/" } },
28
+ %q({"return\\"To\\":":{"\/categories":"\/"}}) => { "return\"To\":" => { "/categories" => "/" } },
29
+ %q({"returnTo":{"\/categories":1}}) => { "returnTo" => { "/categories" => 1 } },
30
+ %({"returnTo":[1,"a"]}) => { "returnTo" => [1, "a"] },
31
+ %({"returnTo":[1,"\\"a\\",", "b"]}) => { "returnTo" => [1, "\"a\",", "b"] },
32
+ %({"a": "'", "b": "5,000"}) => { "a" => "'", "b" => "5,000" },
33
+ %({"a": "a's, b's and c's", "b": "5,000"}) => { "a" => "a's, b's and c's", "b" => "5,000" },
34
+ # multibyte
35
+ %({"matzue": "松江", "asakusa": "浅草"}) => { "matzue" => "松江", "asakusa" => "浅草" },
36
+ %({"a": "2007-01-01"}) => { "a" => Date.new(2007, 1, 1) },
37
+ %({"a": "2007-01-01 01:12:34 Z"}) => { "a" => Time.utc(2007, 1, 1, 1, 12, 34) },
38
+ %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)],
39
+ %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)],
40
+ # no time zone
41
+ %({"a": "2007-01-01 01:12:34"}) => { "a" => Time.new(2007, 1, 1, 1, 12, 34, "-05:00") },
42
+ # invalid date
43
+ %({"a": "1089-10-40"}) => { "a" => "1089-10-40" },
44
+ # xmlschema date notation
45
+ %({"a": "2009-08-10T19:01:02"}) => { "a" => Time.new(2009, 8, 10, 19, 1, 2, "-04:00") },
46
+ %({"a": "2009-08-10T19:01:02Z"}) => { "a" => Time.utc(2009, 8, 10, 19, 1, 2) },
47
+ %({"a": "2009-08-10T19:01:02+02:00"}) => { "a" => Time.utc(2009, 8, 10, 17, 1, 2) },
48
+ %({"a": "2009-08-10T19:01:02-05:00"}) => { "a" => Time.utc(2009, 8, 11, 00, 1, 2) },
49
+ # needs to be *exact*
50
+ %({"a": " 2007-01-01 01:12:34 Z "}) => { "a" => " 2007-01-01 01:12:34 Z " },
51
+ %({"a": "2007-01-01 : it's your birthday"}) => { "a" => "2007-01-01 : it's your birthday" },
52
+ %([]) => [],
53
+ %({}) => {},
54
+ %({"a":1}) => { "a" => 1 },
55
+ %({"a": ""}) => { "a" => "" },
56
+ %({"a":"\\""}) => { "a" => "\"" },
57
+ %({"a": null}) => { "a" => nil },
58
+ %({"a": true}) => { "a" => true },
59
+ %({"a": false}) => { "a" => false },
60
+ '{"bad":"\\\\","trailing":""}' => { "bad" => "\\", "trailing" => "" },
61
+ %q({"a": "http:\/\/test.host\/posts\/1"}) => { "a" => "http://test.host/posts/1" },
62
+ %q({"a": "\u003cunicode\u0020escape\u003e"}) => { "a" => "<unicode escape>" },
63
+ '{"a": "\\\\u0020skip double backslashes"}' => { "a" => "\\u0020skip double backslashes" },
64
+ %q({"a": "\u003cbr /\u003e"}) => { "a" => "<br />" },
65
+ %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => { "b" => ["<i>", "<b>", "<u>"] },
66
+ # test combination of dates and escaped or unicode encoded data in arrays
67
+ %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) =>
68
+ [{ "d" => Date.new(1970, 1, 1), "s" => " escape" }, { "d" => Date.new(1970, 1, 1), "s" => " escape" }],
69
+ %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) =>
70
+ [{ "d" => Date.new(1970, 1, 1), "s" => "http://example.com" },
71
+ { "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }],
72
+ # tests escaping of "\n" char with Yaml backend
73
+ %q({"a":"\n"}) => { "a" => "\n" },
74
+ %q({"a":"\u000a"}) => { "a" => "\n" },
75
+ %q({"a":"Line1\u000aLine2"}) => { "a" => "Line1\nLine2" },
76
+ # prevent json unmarshalling
77
+ '{"json_class":"TestJSONDecoding::Foo"}' => { "json_class" => "TestJSONDecoding::Foo" },
78
+ # json "fragments" - these are invalid JSON, but ActionPack relies on this
79
+ '"a string"' => "a string",
80
+ "1.1" => 1.1,
81
+ "1" => 1,
82
+ "-1" => -1,
83
+ "true" => true,
84
+ "false" => false,
85
+ "null" => nil
86
+ }
87
+
88
+ TESTS.each_with_index do |(json, expected), index|
89
+ fail_message = "JSON decoding failed for #{json}"
90
+
91
+ test "json decodes #{index}" do
92
+ with_tz_default "Eastern Time (US & Canada)" do
93
+ with_parse_json_times(true) do
94
+ silence_warnings do
95
+ if expected.nil?
96
+ assert_nil ActiveSupport::JSON.decode(json), fail_message
97
+ else
98
+ assert_equal expected, ActiveSupport::JSON.decode(json), fail_message
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ test "json decodes time json with time parsing disabled" do
107
+ with_parse_json_times(false) do
108
+ expected = { "a" => "2007-01-01 01:12:34 Z" }
109
+ assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
110
+ end
111
+ end
112
+
113
+ def test_failed_json_decoding
114
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) }
115
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) }
116
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) }
117
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) }
118
+ end
119
+
120
+ def test_cannot_pass_unsupported_options
121
+ assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) }
122
+ end
123
+
124
+ private
125
+
126
+ def with_parse_json_times(value)
127
+ old_value = ActiveSupport.parse_json_times
128
+ ActiveSupport.parse_json_times = value
129
+ yield
130
+ ensure
131
+ ActiveSupport.parse_json_times = old_value
132
+ end
133
+ end
@@ -0,0 +1,507 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require_relative "abstract_unit"
5
+ require "active_support/core_ext/string/inflections"
6
+ require "active_support/json"
7
+ require "active_support/time"
8
+ require_relative "time_zone_test_helpers"
9
+ require_relative "encoding_test_cases"
10
+
11
+ require 'oj'
12
+
13
+ # Sets the ActiveSupport encoder to be Oj and also wraps the setting of
14
+ # globals.
15
+ Oj::Rails.set_encoder()
16
+ #Oj::Rails.optimize(Hash, Array, BigDecimal, Time, Range, Regexp, ActiveSupport::TimeWithZone)
17
+ Oj::Rails.optimize()
18
+
19
+ class TestJSONEncoding < ActiveSupport::TestCase
20
+ include TimeZoneTestHelpers
21
+
22
+ # Added for testing if Oj is used.
23
+ test "oj is used as an encoder" do
24
+ assert_equal ActiveSupport.json_encoder, Oj::Rails::Encoder
25
+ end
26
+
27
+ def sorted_json(json)
28
+ if json.start_with?("{") && json.end_with?("}")
29
+ "{" + json[1..-2].split(",").sort.join(",") + "}"
30
+ else
31
+ json
32
+ end
33
+ end
34
+
35
+ JSONTest::EncodingTestCases.constants.each do |class_tests|
36
+ define_method("test_#{class_tests[0..-6].underscore}") do
37
+ prev = ActiveSupport.use_standard_json_time_format
38
+
39
+ standard_class_tests = /Standard/.match?(class_tests)
40
+
41
+ ActiveSupport.escape_html_entities_in_json = !standard_class_tests
42
+ ActiveSupport.use_standard_json_time_format = standard_class_tests
43
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
44
+ assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
45
+ end
46
+ ensure
47
+ ActiveSupport.escape_html_entities_in_json = false
48
+ ActiveSupport.use_standard_json_time_format = prev
49
+ end
50
+ end
51
+
52
+ def test_process_status
53
+ rubinius_skip "https://github.com/rubinius/rubinius/issues/3334"
54
+
55
+ # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
56
+ # creating a child process, hence this to populate $?
57
+ system("not_a_real_program_#{SecureRandom.hex}")
58
+ assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
59
+ end
60
+
61
+ def test_hash_encoding
62
+ assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(a: :b)
63
+ assert_equal %({\"a\":1}), ActiveSupport::JSON.encode("a" => 1)
64
+ assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1, 2])
65
+ assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
66
+
67
+ assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d))
68
+ end
69
+
70
+ def test_hash_keys_encoding
71
+ ActiveSupport.escape_html_entities_in_json = true
72
+ assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
73
+ ensure
74
+ ActiveSupport.escape_html_entities_in_json = false
75
+ end
76
+
77
+ def test_utf8_string_encoded_properly
78
+ # The original test seems to expect that
79
+ # ActiveSupport.escape_html_entities_in_json reverts to true even after
80
+ # being set to false. I haven't been able to figure that out so the value is
81
+ # set to true, the default, before running the test. This might be wrong but
82
+ # for now it will have to do.
83
+ ActiveSupport.escape_html_entities_in_json = true
84
+ result = ActiveSupport::JSON.encode("€2.99")
85
+ assert_equal '"€2.99"', result
86
+ assert_equal(Encoding::UTF_8, result.encoding)
87
+
88
+ result = ActiveSupport::JSON.encode("✎☺")
89
+ assert_equal '"✎☺"', result
90
+ assert_equal(Encoding::UTF_8, result.encoding)
91
+ end
92
+
93
+ def test_non_utf8_string_transcodes
94
+ s = "二".encode("Shift_JIS")
95
+ result = ActiveSupport::JSON.encode(s)
96
+ assert_equal '"二"', result
97
+ assert_equal Encoding::UTF_8, result.encoding
98
+ end
99
+
100
+ def test_wide_utf8_chars
101
+ w = "𠜎"
102
+ result = ActiveSupport::JSON.encode(w)
103
+ assert_equal '"𠜎"', result
104
+ end
105
+
106
+ def test_wide_utf8_roundtrip
107
+ hash = { string: "𐒑" }
108
+ json = ActiveSupport::JSON.encode(hash)
109
+ decoded_hash = ActiveSupport::JSON.decode(json)
110
+ assert_equal "𐒑", decoded_hash["string"]
111
+ end
112
+
113
+ def test_hash_key_identifiers_are_always_quoted
114
+ values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
115
+ assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
116
+ end
117
+
118
+ def test_hash_should_allow_key_filtering_with_only
119
+ assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, { only: "a" })
120
+ end
121
+
122
+ def test_hash_should_allow_key_filtering_with_except
123
+ assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, { except: ["foo", :c] })
124
+ end
125
+
126
+ def test_time_to_json_includes_local_offset
127
+ with_standard_json_time_format(true) do
128
+ with_env_tz "US/Eastern" do
129
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005, 2, 1, 15, 15, 10))
130
+ end
131
+ end
132
+ end
133
+
134
+ def test_hash_with_time_to_json
135
+ with_standard_json_time_format(false) do
136
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { time: Time.utc(2009) }.to_json
137
+ end
138
+ end
139
+
140
+ def test_nested_hash_with_float
141
+ assert_nothing_raised do
142
+ hash = {
143
+ "CHI" => {
144
+ display_name: "chicago",
145
+ latitude: 123.234
146
+ }
147
+ }
148
+ ActiveSupport::JSON.encode(hash)
149
+ end
150
+ end
151
+
152
+ def test_hash_like_with_options
153
+ h = JSONTest::Hashlike.new
154
+ json = h.to_json only: [:foo]
155
+
156
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
157
+ end
158
+
159
+ def test_object_to_json_with_options
160
+ obj = Object.new
161
+ obj.instance_variable_set :@foo, "hello"
162
+ obj.instance_variable_set :@bar, "world"
163
+ json = obj.to_json only: ["foo"]
164
+
165
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
166
+ end
167
+
168
+ def test_struct_to_json_with_options
169
+ struct = Struct.new(:foo, :bar).new
170
+ struct.foo = "hello"
171
+ struct.bar = "world"
172
+ json = struct.to_json only: [:foo]
173
+
174
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
175
+ end
176
+
177
+ def test_struct_to_json_with_options_nested
178
+ klass = Struct.new(:foo, :bar)
179
+ struct = klass.new "hello", "world"
180
+ parent_struct = klass.new struct, "world"
181
+ json = parent_struct.to_json only: [:foo]
182
+
183
+ assert_equal({ "foo" => { "foo" => "hello" } }, JSON.parse(json))
184
+ end
185
+
186
+
187
+ def test_hash_should_pass_encoding_options_to_children_in_as_json
188
+ person = {
189
+ name: "John",
190
+ address: {
191
+ city: "London",
192
+ country: "UK"
193
+ }
194
+ }
195
+ json = person.as_json only: [:address, :city]
196
+
197
+ assert_equal({ "address" => { "city" => "London" } }, json)
198
+ end
199
+
200
+ def test_hash_should_pass_encoding_options_to_children_in_to_json
201
+ person = {
202
+ name: "John",
203
+ address: {
204
+ city: "London",
205
+ country: "UK"
206
+ }
207
+ }
208
+ json = person.to_json only: [:address, :city]
209
+
210
+ assert_equal(%({"address":{"city":"London"}}), json)
211
+ end
212
+
213
+ def test_array_should_pass_encoding_options_to_children_in_as_json
214
+ people = [
215
+ { name: "John", address: { city: "London", country: "UK" } },
216
+ { name: "Jean", address: { city: "Paris", country: "France" } }
217
+ ]
218
+ json = people.as_json only: [:address, :city]
219
+ expected = [
220
+ { "address" => { "city" => "London" } },
221
+ { "address" => { "city" => "Paris" } }
222
+ ]
223
+
224
+ assert_equal(expected, json)
225
+ end
226
+
227
+ def test_array_should_pass_encoding_options_to_children_in_to_json
228
+ people = [
229
+ { name: "John", address: { city: "London", country: "UK" } },
230
+ { name: "Jean", address: { city: "Paris", country: "France" } }
231
+ ]
232
+ json = people.to_json only: [:address, :city]
233
+
234
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
235
+ end
236
+
237
+ People = Class.new(BasicObject) do
238
+ include Enumerable
239
+ def initialize
240
+ @people = [
241
+ { name: "John", address: { city: "London", country: "UK" } },
242
+ { name: "Jean", address: { city: "Paris", country: "France" } }
243
+ ]
244
+ end
245
+ def each(*, &blk)
246
+ @people.each do |p|
247
+ yield p if blk
248
+ p
249
+ end.each
250
+ end
251
+ end
252
+
253
+ def test_enumerable_should_generate_json_with_as_json
254
+ json = People.new.as_json only: [:address, :city]
255
+ expected = [
256
+ { "address" => { "city" => "London" } },
257
+ { "address" => { "city" => "Paris" } }
258
+ ]
259
+
260
+ assert_equal(expected, json)
261
+ end
262
+
263
+ def test_enumerable_should_generate_json_with_to_json
264
+ json = People.new.to_json only: [:address, :city]
265
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
266
+ end
267
+
268
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
269
+ json = People.new.each.as_json only: [:address, :city]
270
+ expected = [
271
+ { "address" => { "city" => "London" } },
272
+ { "address" => { "city" => "Paris" } }
273
+ ]
274
+
275
+ assert_equal(expected, json)
276
+ end
277
+
278
+ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
279
+ json = People.new.each.to_json only: [:address, :city]
280
+
281
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
282
+ end
283
+
284
+ class CustomWithOptions
285
+ attr_accessor :foo, :bar
286
+
287
+ def as_json(options = {})
288
+ options[:only] = %w(foo bar)
289
+ super(options)
290
+ end
291
+ end
292
+
293
+ def test_hash_to_json_should_not_keep_options_around
294
+ f = CustomWithOptions.new
295
+ f.foo = "hello"
296
+ f.bar = "world"
297
+
298
+ hash = { "foo" => f, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }
299
+ assert_equal({ "foo" => { "foo" => "hello", "bar" => "world" },
300
+ "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }, ActiveSupport::JSON.decode(hash.to_json))
301
+ end
302
+
303
+ def test_array_to_json_should_not_keep_options_around
304
+ f = CustomWithOptions.new
305
+ f.foo = "hello"
306
+ f.bar = "world"
307
+
308
+ array = [f, { "foo" => "other_foo", "test" => "other_test" }]
309
+ assert_equal([{ "foo" => "hello", "bar" => "world" },
310
+ { "foo" => "other_foo", "test" => "other_test" }], ActiveSupport::JSON.decode(array.to_json))
311
+ end
312
+
313
+ class OptionsTest
314
+ def as_json(options = :default)
315
+ options
316
+ end
317
+ end
318
+
319
+ def test_hash_as_json_without_options
320
+ json = { foo: OptionsTest.new }.as_json
321
+ assert_equal({ "foo" => :default }, json)
322
+ end
323
+
324
+ def test_array_as_json_without_options
325
+ json = [ OptionsTest.new ].as_json
326
+ assert_equal([:default], json)
327
+ end
328
+
329
+ def test_struct_encoding
330
+ Struct.new("UserNameAndEmail", :name, :email)
331
+ Struct.new("UserNameAndDate", :name, :date)
332
+ Struct.new("Custom", :name, :sub)
333
+ user_email = Struct::UserNameAndEmail.new "David", "sample@example.com"
334
+ user_birthday = Struct::UserNameAndDate.new "David", Date.new(2010, 01, 01)
335
+ custom = Struct::Custom.new "David", user_birthday
336
+
337
+ json_strings = ""
338
+ json_string_and_date = ""
339
+ json_custom = ""
340
+
341
+ assert_nothing_raised do
342
+ json_strings = user_email.to_json
343
+ json_string_and_date = user_birthday.to_json
344
+ json_custom = custom.to_json
345
+ end
346
+
347
+ assert_equal({ "name" => "David",
348
+ "sub" => {
349
+ "name" => "David",
350
+ "date" => "2010-01-01" } }, ActiveSupport::JSON.decode(json_custom))
351
+
352
+ assert_equal({ "name" => "David", "email" => "sample@example.com" },
353
+ ActiveSupport::JSON.decode(json_strings))
354
+
355
+ assert_equal({ "name" => "David", "date" => "2010-01-01" },
356
+ ActiveSupport::JSON.decode(json_string_and_date))
357
+ end
358
+
359
+ def test_nil_true_and_false_represented_as_themselves
360
+ assert_nil nil.as_json
361
+ assert_equal true, true.as_json
362
+ assert_equal false, false.as_json
363
+ end
364
+
365
+ class HashWithAsJson < Hash
366
+ attr_accessor :as_json_called
367
+
368
+ def initialize(*)
369
+ super
370
+ end
371
+
372
+ def as_json(options = {})
373
+ @as_json_called = true
374
+ super
375
+ end
376
+ end
377
+
378
+ def test_json_gem_dump_by_passing_active_support_encoder
379
+ h = HashWithAsJson.new
380
+ h[:foo] = "hello"
381
+ h[:bar] = "world"
382
+
383
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
384
+ assert_nil h.as_json_called
385
+ end
386
+
387
+ def test_json_gem_generate_by_passing_active_support_encoder
388
+ h = HashWithAsJson.new
389
+ h[:foo] = "hello"
390
+ h[:bar] = "world"
391
+
392
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
393
+ assert_nil h.as_json_called
394
+ end
395
+
396
+ def test_json_gem_pretty_generate_by_passing_active_support_encoder
397
+ h = HashWithAsJson.new
398
+ h[:foo] = "hello"
399
+ h[:bar] = "world"
400
+
401
+ assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
402
+ {
403
+ "foo": "hello",
404
+ "bar": "world"
405
+ }
406
+ EXPECTED
407
+ assert_nil h.as_json_called
408
+ end
409
+
410
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
411
+ with_standard_json_time_format(false) do
412
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
413
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
414
+ assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
415
+ end
416
+ end
417
+
418
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
419
+ with_standard_json_time_format(true) do
420
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
421
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
422
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
423
+ end
424
+ end
425
+
426
+ def test_twz_to_json_with_custom_time_precision
427
+ with_standard_json_time_format(true) do
428
+ with_time_precision(0) do
429
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
430
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
431
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
432
+ end
433
+ end
434
+ end
435
+
436
+ def test_time_to_json_with_custom_time_precision
437
+ with_standard_json_time_format(true) do
438
+ with_time_precision(0) do
439
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
440
+ end
441
+ end
442
+ end
443
+
444
+ def test_datetime_to_json_with_custom_time_precision
445
+ with_standard_json_time_format(true) do
446
+ with_time_precision(0) do
447
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
448
+ end
449
+ end
450
+ end
451
+
452
+ def test_twz_to_json_when_wrapping_a_date_time
453
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
454
+ time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
455
+ assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
456
+ end
457
+
458
+ def test_exception_to_json
459
+ exception = Exception.new("foo")
460
+ assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
461
+ end
462
+
463
+ class InfiniteNumber
464
+ def as_json(options = nil)
465
+ { "number" => Float::INFINITY }
466
+ end
467
+ end
468
+
469
+ def test_to_json_works_when_as_json_returns_infinite_number
470
+ assert_equal '{"number":null}', InfiniteNumber.new.to_json
471
+ end
472
+
473
+ class NaNNumber
474
+ def as_json(options = nil)
475
+ { "number" => Float::NAN }
476
+ end
477
+ end
478
+
479
+ def test_to_json_works_when_as_json_returns_NaN_number
480
+ assert_equal '{"number":null}', NaNNumber.new.to_json
481
+ end
482
+
483
+ def test_to_json_works_on_io_objects
484
+ assert_equal STDOUT.to_s.to_json, STDOUT.to_json
485
+ end
486
+
487
+ private
488
+
489
+ def object_keys(json_object)
490
+ json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
491
+ end
492
+
493
+ def with_standard_json_time_format(boolean = true)
494
+ old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
495
+ yield
496
+ ensure
497
+ ActiveSupport.use_standard_json_time_format = old
498
+ end
499
+
500
+ def with_time_precision(value)
501
+ old_value = ActiveSupport::JSON::Encoding.time_precision
502
+ ActiveSupport::JSON::Encoding.time_precision = value
503
+ yield
504
+ ensure
505
+ ActiveSupport::JSON::Encoding.time_precision = old_value
506
+ end
507
+ end