hash_ext_test.rb 34.0 KB
Newer Older
1
require 'abstract_unit'
J
Jeremy Kemper 已提交
2
require 'active_support/core_ext/hash'
3 4
require 'bigdecimal'
require 'active_support/core_ext/string/access'
5 6
require 'active_support/ordered_hash'
require 'active_support/core_ext/object/conversions'
7 8

class HashExtTest < Test::Unit::TestCase
9 10 11 12
  def setup
    @strings = { 'a' => 1, 'b' => 2 }
    @symbols = { :a  => 1, :b  => 2 }
    @mixed   = { :a  => 1, 'b' => 2 }
13
    @fixnums = {  0  => 1,  1  => 2 }
14 15 16 17 18
    if RUBY_VERSION < '1.9.0'
      @illegal_symbols = { "\0" => 1, "" => 2, [] => 3 }
    else
      @illegal_symbols = { [] => 3 }
    end
19 20 21 22 23 24
  end

  def test_methods
    h = {}
    assert_respond_to h, :symbolize_keys
    assert_respond_to h, :symbolize_keys!
25 26
    assert_respond_to h, :stringify_keys
    assert_respond_to h, :stringify_keys!
27 28 29 30 31 32 33 34 35 36 37 38 39 40
    assert_respond_to h, :to_options
    assert_respond_to h, :to_options!
  end

  def test_symbolize_keys
    assert_equal @symbols, @symbols.symbolize_keys
    assert_equal @symbols, @strings.symbolize_keys
    assert_equal @symbols, @mixed.symbolize_keys
  end

  def test_symbolize_keys!
    assert_equal @symbols, @symbols.dup.symbolize_keys!
    assert_equal @symbols, @strings.dup.symbolize_keys!
    assert_equal @symbols, @mixed.dup.symbolize_keys!
41
  end
42

43 44 45
  def test_symbolize_keys_preserves_keys_that_cant_be_symbolized
    assert_equal @illegal_symbols, @illegal_symbols.symbolize_keys
    assert_equal @illegal_symbols, @illegal_symbols.dup.symbolize_keys!
46 47
  end

48 49 50 51 52
  def test_symbolize_keys_preserves_fixnum_keys
    assert_equal @fixnums, @fixnums.symbolize_keys
    assert_equal @fixnums, @fixnums.dup.symbolize_keys!
  end

53 54 55 56 57 58 59 60 61 62 63 64
  def test_stringify_keys
    assert_equal @strings, @symbols.stringify_keys
    assert_equal @strings, @strings.stringify_keys
    assert_equal @strings, @mixed.stringify_keys
  end

  def test_stringify_keys!
    assert_equal @strings, @symbols.dup.stringify_keys!
    assert_equal @strings, @strings.dup.stringify_keys!
    assert_equal @strings, @mixed.dup.stringify_keys!
  end

65
  def test_symbolize_keys_for_hash_with_indifferent_access
66
    assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
    assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
    assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
  end

  def test_symbolize_keys_bang_for_hash_with_indifferent_access
    assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
    assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
    assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
  end

  def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
    assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
    assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
  end

  def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
    assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys
    assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! }
  end

  def test_stringify_keys_for_hash_with_indifferent_access
89
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
90 91 92 93 94 95
    assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
    assert_equal @strings, @strings.with_indifferent_access.stringify_keys
    assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
  end

  def test_stringify_keys_bang_for_hash_with_indifferent_access
96
    assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
97 98 99
    assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
    assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
    assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
100 101
  end

102
  def test_indifferent_assorted
103 104 105
    @strings = @strings.with_indifferent_access
    @symbols = @symbols.with_indifferent_access
    @mixed   = @mixed.with_indifferent_access
106

107
    assert_equal 'a', @strings.__send__(:convert_key, :a)
108

109 110 111
    assert_equal 1, @strings.fetch('a')
    assert_equal 1, @strings.fetch(:a.to_s)
    assert_equal 1, @strings.fetch(:a)
112

113
    hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
114 115 116
    method_map = { :'[]' => 1, :fetch => 1, :values_at => [1],
      :has_key? => true, :include? => true, :key? => true,
      :member? => true }
117

118 119
    hashes.each do |name, hash|
      method_map.sort_by { |m| m.to_s }.each do |meth, expected|
120
        assert_equal(expected, hash.__send__(meth, 'a'),
121
                     "Calling #{name}.#{meth} 'a'")
122
        assert_equal(expected, hash.__send__(meth, :a),
123
                     "Calling #{name}.#{meth} :a")
124 125
      end
    end
126

127 128 129 130 131 132
    assert_equal [1, 2], @strings.values_at('a', 'b')
    assert_equal [1, 2], @strings.values_at(:a, :b)
    assert_equal [1, 2], @symbols.values_at('a', 'b')
    assert_equal [1, 2], @symbols.values_at(:a, :b)
    assert_equal [1, 2], @mixed.values_at('a', 'b')
    assert_equal [1, 2], @mixed.values_at(:a, :b)
133
  end
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162

  def test_indifferent_reading
    hash = HashWithIndifferentAccess.new
    hash["a"] = 1
    hash["b"] = true
    hash["c"] = false
    hash["d"] = nil

    assert_equal 1, hash[:a]
    assert_equal true, hash[:b]
    assert_equal false, hash[:c]
    assert_equal nil, hash[:d]
    assert_equal nil, hash[:e]
  end

  def test_indifferent_reading_with_nonnil_default
    hash = HashWithIndifferentAccess.new(1)
    hash["a"] = 1
    hash["b"] = true
    hash["c"] = false
    hash["d"] = nil

    assert_equal 1, hash[:a]
    assert_equal true, hash[:b]
    assert_equal false, hash[:c]
    assert_equal nil, hash[:d]
    assert_equal 1, hash[:e]
  end

163 164 165 166 167
  def test_indifferent_writing
    hash = HashWithIndifferentAccess.new
    hash[:a] = 1
    hash['b'] = 2
    hash[3] = 3
168

169 170 171 172 173
    assert_equal hash['a'], 1
    assert_equal hash['b'], 2
    assert_equal hash[:a], 1
    assert_equal hash[:b], 2
    assert_equal hash[3], 3
174 175
  end

176 177 178 179
  def test_indifferent_update
    hash = HashWithIndifferentAccess.new
    hash[:a] = 'a'
    hash['b'] = 'b'
180

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    updated_with_strings = hash.update(@strings)
    updated_with_symbols = hash.update(@symbols)
    updated_with_mixed = hash.update(@mixed)

    assert_equal updated_with_strings[:a], 1
    assert_equal updated_with_strings['a'], 1
    assert_equal updated_with_strings['b'], 2

    assert_equal updated_with_symbols[:a], 1
    assert_equal updated_with_symbols['b'], 2
    assert_equal updated_with_symbols[:b], 2

    assert_equal updated_with_mixed[:a], 1
    assert_equal updated_with_mixed['b'], 2

196
    assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
197 198
  end

199 200 201 202
  def test_indifferent_merging
    hash = HashWithIndifferentAccess.new
    hash[:a] = 'failure'
    hash['b'] = 'failure'
203

204
    other = { 'a' => 1, :b => 2 }
205

206
    merged = hash.merge(other)
207

208 209 210
    assert_equal HashWithIndifferentAccess, merged.class
    assert_equal 1, merged[:a]
    assert_equal 2, merged['b']
211

212
    hash.update(other)
213

214 215 216
    assert_equal 1, hash[:a]
    assert_equal 2, hash['b']
  end
217

218 219 220 221 222 223 224
  def test_indifferent_reverse_merging
    hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value')
    hash.reverse_merge!(:some => 'noclobber', :another => 'clobber')
    assert_equal 'value', hash[:some]
    assert_equal 'clobber', hash[:another]
  end

225 226 227 228 229 230 231 232 233 234
  def test_indifferent_deleting
    get_hash = proc{ { :a => 'foo' }.with_indifferent_access }
    hash = get_hash.call
    assert_equal hash.delete(:a), 'foo'
    assert_equal hash.delete(:a), nil
    hash = get_hash.call
    assert_equal hash.delete('a'), 'foo'
    assert_equal hash.delete('a'), nil
  end

235 236 237 238 239 240 241 242 243 244 245 246
  def test_indifferent_to_hash
    # Should convert to a Hash with String keys.
    assert_equal @strings, @mixed.with_indifferent_access.to_hash

    # Should preserve the default value.
    mixed_with_default = @mixed.dup
    mixed_with_default.default = '1234'
    roundtrip = mixed_with_default.with_indifferent_access.to_hash
    assert_equal @strings, roundtrip
    assert_equal '1234', roundtrip.default
  end

247 248 249 250 251
  def test_indifferent_hash_with_array_of_hashes
    hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
    assert_equal "1", hash[:urls][:url].first[:address]
  end

252 253 254
  def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h[:first] = 1
255
    h = h.stringify_keys
256 257 258
    assert_equal 1, h['first']
    h = HashWithIndifferentAccess.new
    h['first'] = 1
259
    h = h.symbolize_keys
260 261 262
    assert_equal 1, h[:first]
  end

263 264 265 266 267 268 269 270
  def test_to_options_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h['first'] = 1
    h.to_options!
    assert_equal 1, h['first']
  end


271 272 273 274 275 276 277 278
  def test_indifferent_subhashes
    h = {'user' => {'id' => 5}}.with_indifferent_access
    ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}

    h = {:user => {:id => 5}}.with_indifferent_access
    ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
  end

279 280 281
  def test_assert_valid_keys
    assert_nothing_raised do
      { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
282
      { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
283
    end
284

285
    assert_raise(ArgumentError, "Unknown key: failore") do
286
      { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
287
      { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
288 289
    end
  end
290

291 292 293 294 295
  def test_assorted_keys_not_stringified
    original = {Object.new => 2, 1 => 2, [] => true}
    indiff = original.with_indifferent_access
    assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!")
  end
296

297 298 299 300 301 302 303 304 305 306
  def test_deep_merge
    hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }
    hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
    expected = { :a => 1, :b => "b", :c => { :c1 => 2, :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } }
    assert_equal expected, hash_1.deep_merge(hash_2)

    hash_1.deep_merge!(hash_2)
    assert_equal expected, hash_1
  end

307 308 309 310 311 312 313 314 315 316 317 318
  def test_deep_merge_on_indifferent_access
    hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } })
    hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } })
    hash_3 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
    expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
    assert_equal expected, hash_1.deep_merge(hash_2)
    assert_equal expected, hash_1.deep_merge(hash_3)

    hash_1.deep_merge!(hash_2)
    assert_equal expected, hash_1
  end

319 320 321 322 323 324 325 326 327 328
  def test_store_on_indifferent_access
    hash = HashWithIndifferentAccess.new
    hash.store(:test1, 1)
    hash.store('test1', 11)
    hash[:test2] = 2
    hash['test2'] = 22
    expected = { "test1" => 11, "test2" => 22 }
    assert_equal expected, hash
  end

329
  def test_reverse_merge
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
    defaults = { :a => "x", :b => "y", :c => 10 }.freeze
    options  = { :a => 1, :b => 2 }
    expected = { :a => 1, :b => 2, :c => 10 }

    # Should merge defaults into options, creating a new hash.
    assert_equal expected, options.reverse_merge(defaults)
    assert_not_equal expected, options

    # Should merge! defaults into options, replacing options.
    merged = options.dup
    assert_equal expected, merged.reverse_merge!(defaults)
    assert_equal expected, merged

    # Should be an alias for reverse_merge!
    merged = options.dup
    assert_equal expected, merged.reverse_update(defaults)
    assert_equal expected, merged
347
  end
348 349 350 351

  def test_diff
    assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 }))
  end
352 353 354 355 356 357 358 359

  def test_slice
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :a => 'x', :b => 'y' }

    # Should return a new hash with only the given keys.
    assert_equal expected, original.slice(:a, :b)
    assert_not_equal expected, original
360 361 362 363 364
  end

  def test_slice_inplace
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :c => 10 }
365 366 367 368 369

    # Should replace the hash with only the given keys.
    assert_equal expected, original.slice!(:a, :b)
  end

370 371 372 373 374 375 376
  def test_slice_with_an_array_key
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { [:a, :b] => "an array key", :c => 10 }

    # Should return a new hash with only the given keys when given an array key.
    assert_equal expected, original.slice([:a, :b], :c)
    assert_not_equal expected, original
377 378 379 380 381
  end

  def test_slice_inplace_with_an_array_key
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { :a => 'x', :b => 'y' }
382 383 384 385 386 387 388 389 390 391 392 393 394

    # Should replace the hash with only the given keys when given an array key.
    assert_equal expected, original.slice!([:a, :b], :c)
  end

  def test_slice_with_splatted_keys
    original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
    expected = { :a => 'x', :b => "y" }

    # Should grab each of the splatted keys.
    assert_equal expected, original.slice(*[:a, :b])
  end

395 396 397 398 399 400 401 402
  def test_indifferent_slice
    original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
    expected = { :a => 'x', :b => 'y' }.with_indifferent_access

    [['a', 'b'], [:a, :b]].each do |keys|
      # Should return a new hash with only the given keys.
      assert_equal expected, original.slice(*keys), keys.inspect
      assert_not_equal expected, original
403 404
    end
  end
405

406 407 408 409 410
  def test_indifferent_slice_inplace
    original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
    expected = { :c => 10 }.with_indifferent_access

    [['a', 'b'], [:a, :b]].each do |keys|
411 412 413 414 415
      # Should replace the hash with only the given keys.
      copy = original.dup
      assert_equal expected, copy.slice!(*keys)
    end
  end
416

417 418 419 420 421 422 423 424 425 426
  def test_indifferent_slice_access_with_symbols
    original = {'login' => 'bender', 'password' => 'shiny', 'stuff' => 'foo'}
    original = original.with_indifferent_access

    slice = original.slice(:login, :password)

    assert_equal 'bender', slice[:login]
    assert_equal 'bender', slice['login']
  end

427 428 429 430 431 432 433 434 435 436 437 438
  def test_except
    original = { :a => 'x', :b => 'y', :c => 10 }
    expected = { :a => 'x', :b => 'y' }

    # Should return a new hash with only the given keys.
    assert_equal expected, original.except(:c)
    assert_not_equal expected, original

    # Should replace the hash with only the given keys.
    assert_equal expected, original.except!(:c)
    assert_equal expected, original
  end
439 440 441 442 443 444 445

  def test_except_with_original_frozen
    original = { :a => 'x', :b => 'y' }
    original.freeze
    assert_nothing_raised { original.except(:a) }
  end

446 447 448 449
  def test_except_with_mocha_expectation_on_original
    original = { :a => 'x', :b => 'y' }
    original.expects(:delete).never
    original.except(:a)
450
  end
451
end
452

453 454 455 456 457 458 459 460 461 462 463
class IWriteMyOwnXML
  def to_xml(options = {})
    options[:indent] ||= 2
    xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
    xml.instruct! unless options[:skip_instruct]
    xml.level_one do
      xml.tag!(:second_level, 'content')
    end
  end
end

464 465 466 467 468 469 470 471 472 473 474
class HashExtToParamTests < Test::Unit::TestCase
  class ToParam < String
    def to_param
      "#{self}-1"
    end
  end

  def test_string_hash
    assert_equal '', {}.to_param
    assert_equal 'hello=world', { :hello => "world" }.to_param
    assert_equal 'hello=10', { "hello" => 10 }.to_param
475
    assert_equal 'hello=world&say_bye=true', {:hello => "world", "say_bye" => true}.to_param
476 477 478
  end

  def test_number_hash
479
    assert_equal '10=20&30=40&50=60', {10 => 20, 30 => 40, 50 => 60}.to_param
480 481 482
  end

  def test_to_param_hash
483
    assert_equal 'custom2=param2-1&custom=param-1', {ToParam.new('custom') => ToParam.new('param'), ToParam.new('custom2') => ToParam.new('param2')}.to_param
484
  end
485 486 487 488

  def test_to_param_hash_escapes_its_keys_and_values
    assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param
  end
489 490 491 492
  
  def test_to_param_orders_by_key_in_ascending_order
    assert_equal 'a=2&b=1&c=0', ActiveSupport::OrderedHash[*%w(b 1 c 0 a 2)].to_param
  end
493 494
end

495
class HashToXmlTest < Test::Unit::TestCase
496 497 498 499
  def setup
    @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 }
  end

500
  def test_one_level
501 502 503 504
    xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
505 506
  end

507 508 509 510 511 512 513 514 515 516 517 518 519 520
  def test_one_level_dasherize_false
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => false))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street_name>Paulina</street_name>))
    assert xml.include?(%(<name>David</name>))
  end

  def test_one_level_dasherize_true
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => true))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street-name>Paulina</street-name>))
    assert xml.include?(%(<name>David</name>))
  end

521 522 523 524 525 526 527
  def test_one_level_camelize_true
    xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => true))
    assert_equal "<Person>", xml.first(8)
    assert xml.include?(%(<StreetName>Paulina</StreetName>))
    assert xml.include?(%(<Name>David</Name>))
  end

528
  def test_one_level_with_types
529
    xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options)
530 531 532 533
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
    assert xml.include?(%(<age type="integer">26</age>))
534
    assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>))
535
    assert xml.include?(%(<moved-on type="date">2005-11-15</moved-on>))
536
    assert xml.include?(%(<resident type="symbol">yes</resident>))
537 538 539
  end

  def test_one_level_with_nils
540 541 542 543
    xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
544
    assert xml.include?(%(<age nil="true"></age>))
545 546 547 548 549 550 551
  end

  def test_one_level_with_skipping_types
    xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options.merge(:skip_types => true))
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
552
    assert xml.include?(%(<age nil="true"></age>))
553 554
  end

555
  def test_one_level_with_yielding
556 557
    xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) do |x|
      x.creator("Rails")
558 559 560 561 562 563 564 565
    end

    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<street>Paulina</street>))
    assert xml.include?(%(<name>David</name>))
    assert xml.include?(%(<creator>Rails</creator>))
  end

566
  def test_two_levels
567 568 569 570
    xml = { :name => "David", :address => { :street => "Paulina" } }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<name>David</name>))
571
  end
572 573 574 575 576 577 578

  def test_two_levels_with_second_level_overriding_to_xml
    xml = { :name => "David", :address => { :street => "Paulina" }, :child => IWriteMyOwnXML.new }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<level_one><second_level>content</second_level></level_one>))
  end
579

580 581 582
  def test_two_levels_with_array
    xml = { :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
583
    assert xml.include?(%(<addresses type="array"><address>))
584 585 586 587
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<address><street>Evergreen</street></address>))
    assert xml.include?(%(<name>David</name>))
  end
588

589 590
  def test_three_levels_with_array
    xml = { :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] }.to_xml(@xml_options)
591
    assert xml.include?(%(<addresses type="array"><address><streets type="array"><street><name>))
592
  end
593

594 595 596 597 598 599 600 601 602
  def test_timezoned_attributes
    xml = {
      :created_at => Time.utc(1999,2,2),
      :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)')
    }.to_xml(@xml_options)
    assert_match %r{<created-at type=\"datetime\">1999-02-02T00:00:00Z</created-at>}, xml
    assert_match %r{<local-created-at type=\"datetime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml
  end

603 604 605 606 607 608
  def test_single_record_from_xml
    topic_xml = <<-EOT
      <topic>
        <title>The First Topic</title>
        <author-name>David</author-name>
        <id type="integer">1</id>
609
        <approved type="boolean"> true </approved>
610
        <replies-count type="integer">0</replies-count>
611
        <replies-close-in type="integer">2592000000</replies-close-in>
612 613
        <written-on type="date">2003-07-16</written-on>
        <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
614
        <content type="yaml">--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n  should_have_underscores: true\n</content>
615 616
        <author-email-address>david@loudthinking.com</author-email-address>
        <parent-id></parent-id>
617 618
        <ad-revenue type="decimal">1.5</ad-revenue>
        <optimum-viewing-angle type="float">135</optimum-viewing-angle>
619
        <resident type="symbol">yes</resident>
620 621
      </topic>
    EOT
622

623 624 625 626
    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
627
      :approved => true,
628
      :replies_count => 0,
629
      :replies_close_in => 2592000000,
630 631
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
632
      :content => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
633
      :author_email_address => "david@loudthinking.com",
634 635
      :parent_id => nil,
      :ad_revenue => BigDecimal("1.50"),
636 637
      :optimum_viewing_angle => 135.0,
      :resident => :yes
638
    }.stringify_keys
639

640
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
641 642
  end

643 644 645 646 647 648 649 650
  def test_single_record_from_xml_with_nil_values
    topic_xml = <<-EOT
      <topic>
        <title></title>
        <id type="integer"></id>
        <approved type="boolean"></approved>
        <written-on type="date"></written-on>
        <viewed-at type="datetime"></viewed-at>
651
        <content type="yaml"></content>
652 653 654 655 656
        <parent-id></parent-id>
      </topic>
    EOT

    expected_topic_hash = {
657
      :title      => nil,
658 659 660
      :id         => nil,
      :approved   => nil,
      :written_on => nil,
661
      :viewed_at  => nil,
662
      :content    => nil,
663 664 665
      :parent_id  => nil
    }.stringify_keys

666
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
667 668
  end

669 670
  def test_multiple_records_from_xml
    topics_xml = <<-EOT
671
      <topics type="array">
672 673 674 675 676 677
        <topic>
          <title>The First Topic</title>
          <author-name>David</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
678
          <replies-close-in type="integer">2592000000</replies-close-in>
679 680 681 682
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
683
          <parent-id nil="true"></parent-id>
684 685 686 687 688 689 690
        </topic>
        <topic>
          <title>The Second Topic</title>
          <author-name>Jason</author-name>
          <id type="integer">1</id>
          <approved type="boolean">false</approved>
          <replies-count type="integer">0</replies-count>
691
          <replies-close-in type="integer">2592000000</replies-close-in>
692 693 694 695 696 697 698 699
          <written-on type="date">2003-07-16</written-on>
          <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
          <content>Have a nice day</content>
          <author-email-address>david@loudthinking.com</author-email-address>
          <parent-id></parent-id>
        </topic>
      </topics>
    EOT
700

701 702 703 704 705 706
    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
      :approved => false,
      :replies_count => 0,
707
      :replies_close_in => 2592000000,
708 709 710 711 712 713
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
      :content => "Have a nice day",
      :author_email_address => "david@loudthinking.com",
      :parent_id => nil
    }.stringify_keys
714

715
    assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first
716
  end
717

718 719 720
  def test_single_record_from_xml_with_attributes_other_than_type
    topic_xml = <<-EOT
    <rsp stat="ok">
721 722 723
      <photos page="1" pages="1" perpage="100" total="16">
        <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
      </photos>
724 725
    </rsp>
    EOT
726

727 728 729 730 731 732 733 734 735 736
    expected_topic_hash = {
      :id => "175756086",
      :owner => "55569174@N00",
      :secret => "0279bf37a1",
      :server => "76",
      :title => "Colored Pencil PhotoBooth Fun",
      :ispublic => "1",
      :isfriend => "0",
      :isfamily => "0",
    }.stringify_keys
737

738
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"]
739
  end
740

741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
  def test_all_caps_key_from_xml
    test_xml = <<-EOT
      <ABC3XYZ>
        <TEST>Lorem Ipsum</TEST>
      </ABC3XYZ>
    EOT

    expected_hash = {
      "ABC3XYZ" => {
        "TEST" => "Lorem Ipsum"
      }
    }

    assert_equal expected_hash, Hash.from_xml(test_xml)
  end

757 758 759 760 761 762 763 764 765 766
  def test_empty_array_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array"></posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => []}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

767 768 769 770 771 772 773 774 775 776 777
  def test_empty_array_with_whitespace_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => []}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
  def test_array_with_one_entry_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
          <post>a post</post>
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end

  def test_array_with_multiple_entries_from_xml
    blog_xml = <<-XML
      <blog>
        <posts type="array">
          <post>a post</post>
          <post>another post</post>
        </posts>
      </blog>
    XML
    expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
    assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  end
802

803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
  def test_file_from_xml
    blog_xml = <<-XML
      <blog>
        <logo type="file" name="logo.png" content_type="image/png">
        </logo>
      </blog>
    XML
    hash = Hash.from_xml(blog_xml)
    assert hash.has_key?('blog')
    assert hash['blog'].has_key?('logo')

    file = hash['blog']['logo']
    assert_equal 'logo.png', file.original_filename
    assert_equal 'image/png', file.content_type
  end

  def test_file_from_xml_with_defaults
    blog_xml = <<-XML
      <blog>
        <logo type="file">
        </logo>
      </blog>
    XML
    file = Hash.from_xml(blog_xml)['blog']['logo']
    assert_equal 'untitled', file.original_filename
    assert_equal 'application/octet-stream', file.content_type
  end

831 832 833 834 835 836 837 838 839
  def test_xsd_like_types_from_xml
    bacon_xml = <<-EOT
    <bacon>
      <weight type="double">0.5</weight>
      <price type="decimal">12.50</price>
      <chunky type="boolean"> 1 </chunky>
      <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
      <notes type="string"></notes>
      <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
840
      <caption type="binary" encoding="base64">VGhhdCdsbCBkbywgcGlnLg==</caption>
841 842 843 844 845 846 847 848 849
    </bacon>
    EOT

    expected_bacon_hash = {
      :weight => 0.5,
      :chunky => true,
      :price => BigDecimal("12.50"),
      :expires_at => Time.utc(2007,12,25,12,34,56),
      :notes => "",
850 851
      :illustration => "babe.png",
      :caption => "That'll do, pig."
852 853 854 855
    }.stringify_keys

    assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"]
  end
856

857 858 859 860 861
  def test_type_trickles_through_when_unknown
    product_xml = <<-EOT
    <product>
      <weight type="double">0.5</weight>
      <image type="ProductImage"><filename>image.gif</filename></image>
862

863 864 865 866 867 868 869 870
    </product>
    EOT

    expected_product_hash = {
      :weight => 0.5,
      :image => {'type' => 'ProductImage', 'filename' => 'image.gif' },
    }.stringify_keys

871
    assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
872
  end
873

874 875 876 877
  def test_should_use_default_value_for_unknown_key
    hash_wia = HashWithIndifferentAccess.new(3)
    assert_equal 3, hash_wia[:new_key]
  end
878

879 880 881 882
  def test_should_use_default_value_if_no_key_is_supplied
    hash_wia = HashWithIndifferentAccess.new(3)
    assert_equal 3, hash_wia.default
  end
883

884 885 886 887
  def test_should_nil_if_no_default_value_is_supplied
    hash_wia = HashWithIndifferentAccess.new
    assert_nil hash_wia.default
  end
888

889 890 891 892 893
  def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
    hash = Hash.new(3)
    hash_wia = hash.with_indifferent_access
    assert_equal 3, hash_wia.default
  end
894 895 896 897 898 899 900 901 902 903 904

  # The XML builder seems to fail miserably when trying to tag something
  # with the same name as a Kernel method (throw, test, loop, select ...)
  def test_kernel_method_names_to_xml
    hash     = { :throw => { :ball => 'red' } }
    expected = '<person><throw><ball>red</ball></throw></person>'

    assert_nothing_raised do
      assert_equal expected, hash.to_xml(@xml_options)
    end
  end
905 906

  def test_empty_string_works_for_typecast_xml_value
907
    assert_nothing_raised do
908
      Hash.__send__(:typecast_xml_value, "")
909 910
    end
  end
911

912
  def test_escaping_to_xml
913 914
    hash = {
      :bare_string        => 'First & Last Name',
915 916
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys
917

918 919 920
    expected_xml = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
    assert_equal expected_xml, hash.to_xml(@xml_options)
  end
921

922 923
  def test_unescaping_from_xml
    xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
924 925
    expected_hash = {
      :bare_string        => 'First & Last Name',
926 927 928 929
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys
    assert_equal expected_hash, Hash.from_xml(xml_string)['person']
  end
930

931
  def test_roundtrip_to_xml_from_xml
932 933
    hash = {
      :bare_string        => 'First & Last Name',
934 935 936 937 938
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys

    assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person']
  end
939

940 941 942 943 944 945 946 947 948 949
  def test_datetime_xml_type_with_utc_time
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2008-02-10T15:30:45Z</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
  end
950

951 952 953 954 955 956 957 958 959 960
  def test_datetime_xml_type_with_non_utc_time
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2008-02-10T10:30:45-05:00</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
  end
961

962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
  def test_datetime_xml_type_with_far_future_date
    alert_xml = <<-XML
      <alert>
        <alert_at type="datetime">2050-02-10T15:30:45Z</alert_at>
      </alert>
    XML
    alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
    assert alert_at.utc?
    assert_equal 2050,  alert_at.year
    assert_equal 2,     alert_at.month
    assert_equal 10,    alert_at.day
    assert_equal 15,    alert_at.hour
    assert_equal 30,    alert_at.min
    assert_equal 45,    alert_at.sec
  end
977 978 979 980 981 982 983

  def test_to_xml_dups_options
    options = {:skip_instruct => true}
    {}.to_xml(options)
    # :builder, etc, shouldn't be added to options
    assert_equal({:skip_instruct => true}, options)
  end
984

985
  def test_expansion_count_is_limited
J
Jeremy Kemper 已提交
986 987 988 989 990 991 992 993
    expected =
      case ActiveSupport::XmlMini.backend.name
      when 'ActiveSupport::XmlMini_REXML';        RuntimeError
      when 'ActiveSupport::XmlMini_Nokogiri';     Nokogiri::XML::SyntaxError
      when 'ActiveSupport::XmlMini_NokogiriSAX';  RuntimeError
      when 'ActiveSupport::XmlMini_LibXML';       LibXML::XML::Error
      when 'ActiveSupport::XmlMini_LibXMLSAX';    LibXML::XML::Error
      end
994

995
    assert_raise expected do
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
      attack_xml = <<-EOT
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE member [
        <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
        <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
        <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
        <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
        <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
        <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
        <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
      ]>
      <member>
      &a;
      </member>
      EOT
      Hash.from_xml(attack_xml)
    end
  end
J
Jeremy Kemper 已提交
1014
end