hash_ext_test.rb 28.9 KB
Newer Older
1
require 'abstract_unit'
2
require 'builder'
3 4

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

  def test_methods
    h = {}
    assert_respond_to h, :symbolize_keys
    assert_respond_to h, :symbolize_keys!
21 22
    assert_respond_to h, :stringify_keys
    assert_respond_to h, :stringify_keys!
23 24 25 26 27 28 29 30 31 32 33 34 35 36
    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!
37
  end
38

39 40 41
  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!
42 43
  end

44 45 46 47 48
  def test_symbolize_keys_preserves_fixnum_keys
    assert_equal @fixnums, @fixnums.symbolize_keys
    assert_equal @fixnums, @fixnums.dup.symbolize_keys!
  end

49 50 51 52 53 54 55 56 57 58 59 60
  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

61
  def test_indifferent_assorted
62 63 64
    @strings = @strings.with_indifferent_access
    @symbols = @symbols.with_indifferent_access
    @mixed   = @mixed.with_indifferent_access
65

66
    assert_equal 'a', @strings.__send__(:convert_key, :a)
67

68 69 70
    assert_equal 1, @strings.fetch('a')
    assert_equal 1, @strings.fetch(:a.to_s)
    assert_equal 1, @strings.fetch(:a)
71

72
    hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
73 74 75
    method_map = { :'[]' => 1, :fetch => 1, :values_at => [1],
      :has_key? => true, :include? => true, :key? => true,
      :member? => true }
76

77 78
    hashes.each do |name, hash|
      method_map.sort_by { |m| m.to_s }.each do |meth, expected|
79
        assert_equal(expected, hash.__send__(meth, 'a'),
80
                     "Calling #{name}.#{meth} 'a'")
81
        assert_equal(expected, hash.__send__(meth, :a),
82
                     "Calling #{name}.#{meth} :a")
83 84
      end
    end
85

86 87 88 89 90 91
    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)
92
  end
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

  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

122 123 124 125 126
  def test_indifferent_writing
    hash = HashWithIndifferentAccess.new
    hash[:a] = 1
    hash['b'] = 2
    hash[3] = 3
127

128 129 130 131 132
    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
133 134
  end

135 136 137 138
  def test_indifferent_update
    hash = HashWithIndifferentAccess.new
    hash[:a] = 'a'
    hash['b'] = 'b'
139

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    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

155
    assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
156 157
  end

158 159 160 161
  def test_indifferent_merging
    hash = HashWithIndifferentAccess.new
    hash[:a] = 'failure'
    hash['b'] = 'failure'
162

163
    other = { 'a' => 1, :b => 2 }
164

165
    merged = hash.merge(other)
166

167 168 169
    assert_equal HashWithIndifferentAccess, merged.class
    assert_equal 1, merged[:a]
    assert_equal 2, merged['b']
170

171
    hash.update(other)
172

173 174 175
    assert_equal 1, hash[:a]
    assert_equal 2, hash['b']
  end
176

177 178 179 180 181 182 183 184 185 186
  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

187 188 189 190 191 192 193 194 195 196 197 198
  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

199 200 201 202 203
  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

204 205 206 207 208 209 210 211 212 213 214
  def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h[:first] = 1
    h.stringify_keys!
    assert_equal 1, h['first']
    h = HashWithIndifferentAccess.new
    h['first'] = 1
    h.symbolize_keys!
    assert_equal 1, h[:first]
  end

215 216 217 218 219 220 221 222
  def test_to_options_on_indifferent_preserves_hash
    h = HashWithIndifferentAccess.new
    h['first'] = 1
    h.to_options!
    assert_equal 1, h['first']
  end


223 224 225 226 227 228 229 230
  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

231 232 233
  def test_assert_valid_keys
    assert_nothing_raised do
      { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
234
      { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
235
    end
236

237 238
    assert_raises(ArgumentError, "Unknown key(s): failore") do
      { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
239
      { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
240 241
    end
  end
242

243 244 245 246 247
  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
248

249 250 251 252 253 254 255 256 257 258
  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

259
  def test_reverse_merge
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
    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
277
  end
278 279 280 281

  def test_diff
    assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 }))
  end
282 283 284 285 286 287 288 289 290 291 292 293 294 295

  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

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

296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
  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

    # Should replace the hash with only the given keys when given an array key.
    assert_equal expected, original.slice!([:a, :b], :c)
    assert_equal expected, original
  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

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
  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

      # Should replace the hash with only the given keys.
      copy = original.dup
      assert_equal expected, copy.slice!(*keys)
      assert_equal expected, copy
    end
  end
332

333 334 335 336 337 338 339 340 341 342
  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

343 344 345 346 347 348 349 350 351 352 353 354
  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
355 356 357 358 359 360 361

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

362 363 364 365
  def test_except_with_mocha_expectation_on_original
    original = { :a => 'x', :b => 'y' }
    original.expects(:delete).never
    original.except(:a)
366
  end
367
end
368

369 370 371 372 373 374 375 376 377 378 379
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

380
class HashToXmlTest < Test::Unit::TestCase
381 382 383 384
  def setup
    @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 }
  end

385
  def test_one_level
386 387 388 389
    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>))
390 391
  end

392 393 394 395 396 397 398 399 400 401 402 403 404 405
  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

406 407 408 409 410 411 412
  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

413
  def test_one_level_with_types
414
    xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options)
415 416 417 418
    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>))
419
    assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>))
420
    assert xml.include?(%(<moved-on type="date">2005-11-15</moved-on>))
421
    assert xml.include?(%(<resident type="symbol">yes</resident>))
422 423 424
  end

  def test_one_level_with_nils
425 426 427 428
    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>))
429
    assert xml.include?(%(<age nil="true"></age>))
430 431 432 433 434 435 436
  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>))
437
    assert xml.include?(%(<age nil="true"></age>))
438 439
  end

440
  def test_one_level_with_yielding
441 442
    xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) do |x|
      x.creator("Rails")
443 444 445 446 447 448 449 450
    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

451
  def test_two_levels
452 453 454 455
    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>))
456
  end
457 458 459 460 461 462 463

  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
464

465 466 467
  def test_two_levels_with_array
    xml = { :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] }.to_xml(@xml_options)
    assert_equal "<person>", xml.first(8)
468
    assert xml.include?(%(<addresses type="array"><address>))
469 470 471 472
    assert xml.include?(%(<address><street>Paulina</street></address>))
    assert xml.include?(%(<address><street>Evergreen</street></address>))
    assert xml.include?(%(<name>David</name>))
  end
473

474 475
  def test_three_levels_with_array
    xml = { :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] }.to_xml(@xml_options)
476
    assert xml.include?(%(<addresses type="array"><address><streets type="array"><street><name>))
477
  end
478

479 480 481 482 483 484
  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>
485
        <approved type="boolean"> true </approved>
486
        <replies-count type="integer">0</replies-count>
487
        <replies-close-in type="integer">2592000000</replies-close-in>
488 489
        <written-on type="date">2003-07-16</written-on>
        <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
490
        <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>
491 492
        <author-email-address>david@loudthinking.com</author-email-address>
        <parent-id></parent-id>
493 494
        <ad-revenue type="decimal">1.5</ad-revenue>
        <optimum-viewing-angle type="float">135</optimum-viewing-angle>
495
        <resident type="symbol">yes</resident>
496 497
      </topic>
    EOT
498

499 500 501 502
    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
503
      :approved => true,
504
      :replies_count => 0,
505
      :replies_close_in => 2592000000,
506 507
      :written_on => Date.new(2003, 7, 16),
      :viewed_at => Time.utc(2003, 7, 16, 9, 28),
508
      :content => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
509
      :author_email_address => "david@loudthinking.com",
510 511
      :parent_id => nil,
      :ad_revenue => BigDecimal("1.50"),
512 513
      :optimum_viewing_angle => 135.0,
      :resident => :yes
514
    }.stringify_keys
515

516
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
517 518
  end

519 520 521 522 523 524 525 526
  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>
527
        <content type="yaml"></content>
528 529 530 531 532
        <parent-id></parent-id>
      </topic>
    EOT

    expected_topic_hash = {
533
      :title      => nil, 
534 535 536
      :id         => nil,
      :approved   => nil,
      :written_on => nil,
537
      :viewed_at  => nil,
538
      :content    => nil, 
539 540 541
      :parent_id  => nil
    }.stringify_keys

542
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
543 544
  end

545 546
  def test_multiple_records_from_xml
    topics_xml = <<-EOT
547
      <topics type="array">
548 549 550 551 552 553
        <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>
554
          <replies-close-in type="integer">2592000000</replies-close-in>
555 556 557 558
          <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>
559
          <parent-id nil="true"></parent-id>
560 561 562 563 564 565 566
        </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>
567
          <replies-close-in type="integer">2592000000</replies-close-in>
568 569 570 571 572 573 574 575
          <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
576

577 578 579 580 581 582
    expected_topic_hash = {
      :title => "The First Topic",
      :author_name => "David",
      :id => 1,
      :approved => false,
      :replies_count => 0,
583
      :replies_close_in => 2592000000,
584 585 586 587 588 589
      :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
590

591
    assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first
592
  end
593

594 595 596
  def test_single_record_from_xml_with_attributes_other_than_type
    topic_xml = <<-EOT
    <rsp stat="ok">
597 598 599
      <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>
600 601
    </rsp>
    EOT
602

603 604 605 606 607 608 609 610 611 612
    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
613

614
    assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"]
615
  end
616
  
617 618 619 620 621 622 623 624 625 626
  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

627 628 629 630 631 632 633 634 635 636 637
  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

638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
  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
662

663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
  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

691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
  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>
    </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 => "",
      :illustration => "babe.png"
    }.stringify_keys

    assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"]
  end
714
  
715 716 717 718 719
  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>
720
      
721 722 723 724 725 726 727 728
    </product>
    EOT

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

729
    assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]    
730
  end
731

732 733 734 735
  def test_should_use_default_value_for_unknown_key
    hash_wia = HashWithIndifferentAccess.new(3)
    assert_equal 3, hash_wia[:new_key]
  end
736

737 738 739 740
  def test_should_use_default_value_if_no_key_is_supplied
    hash_wia = HashWithIndifferentAccess.new(3)
    assert_equal 3, hash_wia.default
  end
741

742 743 744 745
  def test_should_nil_if_no_default_value_is_supplied
    hash_wia = HashWithIndifferentAccess.new
    assert_nil hash_wia.default
  end
746

747 748 749 750 751
  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
752 753 754 755 756 757 758 759 760 761 762

  # 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
763 764
  
  def test_empty_string_works_for_typecast_xml_value    
765
    assert_nothing_raised do
766
      Hash.__send__(:typecast_xml_value, "")
767 768
    end
  end
769
  
770
  def test_escaping_to_xml
771 772
    hash = { 
      :bare_string        => 'First & Last Name', 
773 774
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys
775
    
776 777 778
    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
779
  
780 781
  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>'
782 783
    expected_hash = { 
      :bare_string        => 'First & Last Name', 
784 785 786 787
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys
    assert_equal expected_hash, Hash.from_xml(xml_string)['person']
  end
788
  
789
  def test_roundtrip_to_xml_from_xml
790 791
    hash = { 
      :bare_string        => 'First & Last Name', 
792 793 794 795 796
      :pre_escaped_string => 'First &amp; Last Name'
    }.stringify_keys

    assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person']
  end
797
  
798 799 800 801 802 803 804 805 806 807
  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
808
  
809 810 811 812 813 814 815 816 817 818
  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
819
  
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
  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
835
end
836 837 838

class QueryTest < Test::Unit::TestCase
  def test_simple_conversion
839 840 841 842 843 844 845 846 847 848 849
    assert_query_equal 'a=10', :a => 10
  end

  def test_cgi_escaping
    assert_query_equal 'a%3Ab=c+d', 'a:b' => 'c d'
  end

  def test_nil_parameter_value
    empty = Object.new
    def empty.to_param; nil end
    assert_query_equal 'a=', 'a' => empty
850
  end
J
Jeremy Kemper 已提交
851

852
  def test_nested_conversion
853
    assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas',
854
      :person => {:name => 'Nicholas', :login => 'seckar'}
855
  end
J
Jeremy Kemper 已提交
856

857
  def test_multiple_nested
858 859
    assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10',
      :person => {:id => 10}, :account => {:person => {:id => 20}}
860
  end
J
Jeremy Kemper 已提交
861

862
  def test_array_values
863 864
    assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20',
      :person => {:id => [10, 20]}
865
  end
866

867 868 869 870 871
  def test_array_values_are_not_sorted
    assert_query_equal 'person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10',
      :person => {:id => [20, 10]}
  end

872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
  def test_expansion_count_is_limited
    assert_raises RuntimeError do
      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

893 894
  private
    def assert_query_equal(expected, actual, message = nil)
895
      assert_equal expected.split('&'), actual.to_query.split('&')
896
    end
J
Jeremy Kemper 已提交
897
end