PageRenderTime 132ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 1ms

/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb

https://github.com/applicake/instantcompanyhandbook
Ruby | 1074 lines | 920 code | 140 blank | 14 comment | 2 complexity | dfd334d8d2383365c7b2da93fdf5f4fd MD5 | raw file
Possible License(s): ISC
  1. require 'abstract_unit'
  2. require 'active_support/core_ext/hash'
  3. require 'bigdecimal'
  4. require 'active_support/core_ext/string/access'
  5. require 'active_support/ordered_hash'
  6. require 'active_support/core_ext/object/conversions'
  7. require 'active_support/inflections'
  8. class HashExtTest < Test::Unit::TestCase
  9. class IndifferentHash < HashWithIndifferentAccess
  10. end
  11. class SubclassingArray < Array
  12. end
  13. class SubclassingHash < Hash
  14. end
  15. def setup
  16. @strings = { 'a' => 1, 'b' => 2 }
  17. @symbols = { :a => 1, :b => 2 }
  18. @mixed = { :a => 1, 'b' => 2 }
  19. @fixnums = { 0 => 1, 1 => 2 }
  20. if RUBY_VERSION < '1.9.0'
  21. @illegal_symbols = { "\0" => 1, "" => 2, [] => 3 }
  22. else
  23. @illegal_symbols = { [] => 3 }
  24. end
  25. end
  26. def test_methods
  27. h = {}
  28. assert_respond_to h, :symbolize_keys
  29. assert_respond_to h, :symbolize_keys!
  30. assert_respond_to h, :stringify_keys
  31. assert_respond_to h, :stringify_keys!
  32. assert_respond_to h, :to_options
  33. assert_respond_to h, :to_options!
  34. end
  35. def test_symbolize_keys
  36. assert_equal @symbols, @symbols.symbolize_keys
  37. assert_equal @symbols, @strings.symbolize_keys
  38. assert_equal @symbols, @mixed.symbolize_keys
  39. end
  40. def test_symbolize_keys!
  41. assert_equal @symbols, @symbols.dup.symbolize_keys!
  42. assert_equal @symbols, @strings.dup.symbolize_keys!
  43. assert_equal @symbols, @mixed.dup.symbolize_keys!
  44. end
  45. def test_symbolize_keys_preserves_keys_that_cant_be_symbolized
  46. assert_equal @illegal_symbols, @illegal_symbols.symbolize_keys
  47. assert_equal @illegal_symbols, @illegal_symbols.dup.symbolize_keys!
  48. end
  49. def test_symbolize_keys_preserves_fixnum_keys
  50. assert_equal @fixnums, @fixnums.symbolize_keys
  51. assert_equal @fixnums, @fixnums.dup.symbolize_keys!
  52. end
  53. def test_stringify_keys
  54. assert_equal @strings, @symbols.stringify_keys
  55. assert_equal @strings, @strings.stringify_keys
  56. assert_equal @strings, @mixed.stringify_keys
  57. end
  58. def test_stringify_keys!
  59. assert_equal @strings, @symbols.dup.stringify_keys!
  60. assert_equal @strings, @strings.dup.stringify_keys!
  61. assert_equal @strings, @mixed.dup.stringify_keys!
  62. end
  63. def test_symbolize_keys_for_hash_with_indifferent_access
  64. assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
  65. assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
  66. assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
  67. assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
  68. end
  69. def test_symbolize_keys_bang_for_hash_with_indifferent_access
  70. assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
  71. assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
  72. assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
  73. end
  74. def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
  75. assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
  76. assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
  77. end
  78. def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
  79. assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys
  80. assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! }
  81. end
  82. def test_stringify_keys_for_hash_with_indifferent_access
  83. assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
  84. assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
  85. assert_equal @strings, @strings.with_indifferent_access.stringify_keys
  86. assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
  87. end
  88. def test_stringify_keys_bang_for_hash_with_indifferent_access
  89. assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
  90. assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
  91. assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
  92. assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
  93. end
  94. def test_hash_subclass
  95. flash = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
  96. assert_kind_of SubclassingHash, flash["foo"]
  97. end
  98. def test_indifferent_assorted
  99. @strings = @strings.with_indifferent_access
  100. @symbols = @symbols.with_indifferent_access
  101. @mixed = @mixed.with_indifferent_access
  102. assert_equal 'a', @strings.__send__(:convert_key, :a)
  103. assert_equal 1, @strings.fetch('a')
  104. assert_equal 1, @strings.fetch(:a.to_s)
  105. assert_equal 1, @strings.fetch(:a)
  106. hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
  107. method_map = { :'[]' => 1, :fetch => 1, :values_at => [1],
  108. :has_key? => true, :include? => true, :key? => true,
  109. :member? => true }
  110. hashes.each do |name, hash|
  111. method_map.sort_by { |m| m.to_s }.each do |meth, expected|
  112. assert_equal(expected, hash.__send__(meth, 'a'),
  113. "Calling #{name}.#{meth} 'a'")
  114. assert_equal(expected, hash.__send__(meth, :a),
  115. "Calling #{name}.#{meth} :a")
  116. end
  117. end
  118. assert_equal [1, 2], @strings.values_at('a', 'b')
  119. assert_equal [1, 2], @strings.values_at(:a, :b)
  120. assert_equal [1, 2], @symbols.values_at('a', 'b')
  121. assert_equal [1, 2], @symbols.values_at(:a, :b)
  122. assert_equal [1, 2], @mixed.values_at('a', 'b')
  123. assert_equal [1, 2], @mixed.values_at(:a, :b)
  124. end
  125. def test_indifferent_reading
  126. hash = HashWithIndifferentAccess.new
  127. hash["a"] = 1
  128. hash["b"] = true
  129. hash["c"] = false
  130. hash["d"] = nil
  131. assert_equal 1, hash[:a]
  132. assert_equal true, hash[:b]
  133. assert_equal false, hash[:c]
  134. assert_equal nil, hash[:d]
  135. assert_equal nil, hash[:e]
  136. end
  137. def test_indifferent_reading_with_nonnil_default
  138. hash = HashWithIndifferentAccess.new(1)
  139. hash["a"] = 1
  140. hash["b"] = true
  141. hash["c"] = false
  142. hash["d"] = nil
  143. assert_equal 1, hash[:a]
  144. assert_equal true, hash[:b]
  145. assert_equal false, hash[:c]
  146. assert_equal nil, hash[:d]
  147. assert_equal 1, hash[:e]
  148. end
  149. def test_indifferent_writing
  150. hash = HashWithIndifferentAccess.new
  151. hash[:a] = 1
  152. hash['b'] = 2
  153. hash[3] = 3
  154. assert_equal hash['a'], 1
  155. assert_equal hash['b'], 2
  156. assert_equal hash[:a], 1
  157. assert_equal hash[:b], 2
  158. assert_equal hash[3], 3
  159. end
  160. def test_indifferent_update
  161. hash = HashWithIndifferentAccess.new
  162. hash[:a] = 'a'
  163. hash['b'] = 'b'
  164. updated_with_strings = hash.update(@strings)
  165. updated_with_symbols = hash.update(@symbols)
  166. updated_with_mixed = hash.update(@mixed)
  167. assert_equal updated_with_strings[:a], 1
  168. assert_equal updated_with_strings['a'], 1
  169. assert_equal updated_with_strings['b'], 2
  170. assert_equal updated_with_symbols[:a], 1
  171. assert_equal updated_with_symbols['b'], 2
  172. assert_equal updated_with_symbols[:b], 2
  173. assert_equal updated_with_mixed[:a], 1
  174. assert_equal updated_with_mixed['b'], 2
  175. assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
  176. end
  177. def test_indifferent_merging
  178. hash = HashWithIndifferentAccess.new
  179. hash[:a] = 'failure'
  180. hash['b'] = 'failure'
  181. other = { 'a' => 1, :b => 2 }
  182. merged = hash.merge(other)
  183. assert_equal HashWithIndifferentAccess, merged.class
  184. assert_equal 1, merged[:a]
  185. assert_equal 2, merged['b']
  186. hash.update(other)
  187. assert_equal 1, hash[:a]
  188. assert_equal 2, hash['b']
  189. end
  190. def test_indifferent_reverse_merging
  191. hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value')
  192. hash.reverse_merge!(:some => 'noclobber', :another => 'clobber')
  193. assert_equal 'value', hash[:some]
  194. assert_equal 'clobber', hash[:another]
  195. end
  196. def test_indifferent_deleting
  197. get_hash = proc{ { :a => 'foo' }.with_indifferent_access }
  198. hash = get_hash.call
  199. assert_equal hash.delete(:a), 'foo'
  200. assert_equal hash.delete(:a), nil
  201. hash = get_hash.call
  202. assert_equal hash.delete('a'), 'foo'
  203. assert_equal hash.delete('a'), nil
  204. end
  205. def test_indifferent_to_hash
  206. # Should convert to a Hash with String keys.
  207. assert_equal @strings, @mixed.with_indifferent_access.to_hash
  208. # Should preserve the default value.
  209. mixed_with_default = @mixed.dup
  210. mixed_with_default.default = '1234'
  211. roundtrip = mixed_with_default.with_indifferent_access.to_hash
  212. assert_equal @strings, roundtrip
  213. assert_equal '1234', roundtrip.default
  214. end
  215. def test_indifferent_hash_with_array_of_hashes
  216. hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
  217. assert_equal "1", hash[:urls][:url].first[:address]
  218. end
  219. def test_should_preserve_array_subclass_when_value_is_array
  220. array = SubclassingArray.new
  221. array << { "address" => "1" }
  222. hash = { "urls" => { "url" => array }}.with_indifferent_access
  223. assert_equal SubclassingArray, hash[:urls][:url].class
  224. end
  225. def test_should_preserve_array_class_when_hash_value_is_frozen_array
  226. array = SubclassingArray.new
  227. array << { "address" => "1" }
  228. hash = { "urls" => { "url" => array.freeze }}.with_indifferent_access
  229. assert_equal SubclassingArray, hash[:urls][:url].class
  230. end
  231. def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
  232. h = HashWithIndifferentAccess.new
  233. h[:first] = 1
  234. h = h.stringify_keys
  235. assert_equal 1, h['first']
  236. h = HashWithIndifferentAccess.new
  237. h['first'] = 1
  238. h = h.symbolize_keys
  239. assert_equal 1, h[:first]
  240. end
  241. def test_to_options_on_indifferent_preserves_hash
  242. h = HashWithIndifferentAccess.new
  243. h['first'] = 1
  244. h.to_options!
  245. assert_equal 1, h['first']
  246. end
  247. def test_indifferent_subhashes
  248. h = {'user' => {'id' => 5}}.with_indifferent_access
  249. ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
  250. h = {:user => {:id => 5}}.with_indifferent_access
  251. ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
  252. end
  253. def test_assert_valid_keys
  254. assert_nothing_raised do
  255. { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
  256. { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
  257. end
  258. assert_raise(ArgumentError, "Unknown key(s): failore") do
  259. { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
  260. { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
  261. end
  262. end
  263. def test_assorted_keys_not_stringified
  264. original = {Object.new => 2, 1 => 2, [] => true}
  265. indiff = original.with_indifferent_access
  266. assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!")
  267. end
  268. def test_deep_merge
  269. hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }
  270. hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
  271. expected = { :a => 1, :b => "b", :c => { :c1 => 2, :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } }
  272. assert_equal expected, hash_1.deep_merge(hash_2)
  273. hash_1.deep_merge!(hash_2)
  274. assert_equal expected, hash_1
  275. end
  276. def test_deep_merge_on_indifferent_access
  277. hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } })
  278. hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } })
  279. hash_3 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
  280. expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
  281. assert_equal expected, hash_1.deep_merge(hash_2)
  282. assert_equal expected, hash_1.deep_merge(hash_3)
  283. hash_1.deep_merge!(hash_2)
  284. assert_equal expected, hash_1
  285. end
  286. def test_store_on_indifferent_access
  287. hash = HashWithIndifferentAccess.new
  288. hash.store(:test1, 1)
  289. hash.store('test1', 11)
  290. hash[:test2] = 2
  291. hash['test2'] = 22
  292. expected = { "test1" => 11, "test2" => 22 }
  293. assert_equal expected, hash
  294. end
  295. def test_reverse_merge
  296. defaults = { :a => "x", :b => "y", :c => 10 }.freeze
  297. options = { :a => 1, :b => 2 }
  298. expected = { :a => 1, :b => 2, :c => 10 }
  299. # Should merge defaults into options, creating a new hash.
  300. assert_equal expected, options.reverse_merge(defaults)
  301. assert_not_equal expected, options
  302. # Should merge! defaults into options, replacing options.
  303. merged = options.dup
  304. assert_equal expected, merged.reverse_merge!(defaults)
  305. assert_equal expected, merged
  306. # Should be an alias for reverse_merge!
  307. merged = options.dup
  308. assert_equal expected, merged.reverse_update(defaults)
  309. assert_equal expected, merged
  310. end
  311. def test_diff
  312. assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 }))
  313. end
  314. def test_slice
  315. original = { :a => 'x', :b => 'y', :c => 10 }
  316. expected = { :a => 'x', :b => 'y' }
  317. # Should return a new hash with only the given keys.
  318. assert_equal expected, original.slice(:a, :b)
  319. assert_not_equal expected, original
  320. end
  321. def test_slice_inplace
  322. original = { :a => 'x', :b => 'y', :c => 10 }
  323. expected = { :c => 10 }
  324. # Should replace the hash with only the given keys.
  325. assert_equal expected, original.slice!(:a, :b)
  326. end
  327. def test_slice_with_an_array_key
  328. original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
  329. expected = { [:a, :b] => "an array key", :c => 10 }
  330. # Should return a new hash with only the given keys when given an array key.
  331. assert_equal expected, original.slice([:a, :b], :c)
  332. assert_not_equal expected, original
  333. end
  334. def test_slice_inplace_with_an_array_key
  335. original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
  336. expected = { :a => 'x', :b => 'y' }
  337. # Should replace the hash with only the given keys when given an array key.
  338. assert_equal expected, original.slice!([:a, :b], :c)
  339. end
  340. def test_slice_with_splatted_keys
  341. original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
  342. expected = { :a => 'x', :b => "y" }
  343. # Should grab each of the splatted keys.
  344. assert_equal expected, original.slice(*[:a, :b])
  345. end
  346. def test_indifferent_slice
  347. original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
  348. expected = { :a => 'x', :b => 'y' }.with_indifferent_access
  349. [['a', 'b'], [:a, :b]].each do |keys|
  350. # Should return a new hash with only the given keys.
  351. assert_equal expected, original.slice(*keys), keys.inspect
  352. assert_not_equal expected, original
  353. end
  354. end
  355. def test_indifferent_slice_inplace
  356. original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
  357. expected = { :c => 10 }.with_indifferent_access
  358. [['a', 'b'], [:a, :b]].each do |keys|
  359. # Should replace the hash with only the given keys.
  360. copy = original.dup
  361. assert_equal expected, copy.slice!(*keys)
  362. end
  363. end
  364. def test_indifferent_slice_access_with_symbols
  365. original = {'login' => 'bender', 'password' => 'shiny', 'stuff' => 'foo'}
  366. original = original.with_indifferent_access
  367. slice = original.slice(:login, :password)
  368. assert_equal 'bender', slice[:login]
  369. assert_equal 'bender', slice['login']
  370. end
  371. def test_except
  372. original = { :a => 'x', :b => 'y', :c => 10 }
  373. expected = { :a => 'x', :b => 'y' }
  374. # Should return a new hash with only the given keys.
  375. assert_equal expected, original.except(:c)
  376. assert_not_equal expected, original
  377. # Should replace the hash with only the given keys.
  378. assert_equal expected, original.except!(:c)
  379. assert_equal expected, original
  380. end
  381. def test_except_with_original_frozen
  382. original = { :a => 'x', :b => 'y' }
  383. original.freeze
  384. assert_nothing_raised { original.except(:a) }
  385. end
  386. def test_except_with_mocha_expectation_on_original
  387. original = { :a => 'x', :b => 'y' }
  388. original.expects(:delete).never
  389. original.except(:a)
  390. end
  391. end
  392. class IWriteMyOwnXML
  393. def to_xml(options = {})
  394. options[:indent] ||= 2
  395. xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
  396. xml.instruct! unless options[:skip_instruct]
  397. xml.level_one do
  398. xml.tag!(:second_level, 'content')
  399. end
  400. end
  401. end
  402. class HashExtToParamTests < Test::Unit::TestCase
  403. class ToParam < String
  404. def to_param
  405. "#{self}-1"
  406. end
  407. end
  408. def test_string_hash
  409. assert_equal '', {}.to_param
  410. assert_equal 'hello=world', { :hello => "world" }.to_param
  411. assert_equal 'hello=10', { "hello" => 10 }.to_param
  412. assert_equal 'hello=world&say_bye=true', {:hello => "world", "say_bye" => true}.to_param
  413. end
  414. def test_number_hash
  415. assert_equal '10=20&30=40&50=60', {10 => 20, 30 => 40, 50 => 60}.to_param
  416. end
  417. def test_to_param_hash
  418. assert_equal 'custom2=param2-1&custom=param-1', {ToParam.new('custom') => ToParam.new('param'), ToParam.new('custom2') => ToParam.new('param2')}.to_param
  419. end
  420. def test_to_param_hash_escapes_its_keys_and_values
  421. 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
  422. end
  423. def test_to_param_orders_by_key_in_ascending_order
  424. assert_equal 'a=2&b=1&c=0', ActiveSupport::OrderedHash[*%w(b 1 c 0 a 2)].to_param
  425. end
  426. end
  427. class HashToXmlTest < Test::Unit::TestCase
  428. def setup
  429. @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 }
  430. end
  431. def test_one_level
  432. xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options)
  433. assert_equal "<person>", xml.first(8)
  434. assert xml.include?(%(<street>Paulina</street>))
  435. assert xml.include?(%(<name>David</name>))
  436. end
  437. def test_one_level_dasherize_false
  438. xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => false))
  439. assert_equal "<person>", xml.first(8)
  440. assert xml.include?(%(<street_name>Paulina</street_name>))
  441. assert xml.include?(%(<name>David</name>))
  442. end
  443. def test_one_level_dasherize_true
  444. xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => true))
  445. assert_equal "<person>", xml.first(8)
  446. assert xml.include?(%(<street-name>Paulina</street-name>))
  447. assert xml.include?(%(<name>David</name>))
  448. end
  449. def test_one_level_camelize_true
  450. xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => true))
  451. assert_equal "<Person>", xml.first(8)
  452. assert xml.include?(%(<StreetName>Paulina</StreetName>))
  453. assert xml.include?(%(<Name>David</Name>))
  454. end
  455. def test_one_level_with_types
  456. xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options)
  457. assert_equal "<person>", xml.first(8)
  458. assert xml.include?(%(<street>Paulina</street>))
  459. assert xml.include?(%(<name>David</name>))
  460. assert xml.include?(%(<age type="integer">26</age>))
  461. assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>))
  462. assert xml.include?(%(<moved-on type="date">2005-11-15</moved-on>))
  463. assert xml.include?(%(<resident type="symbol">yes</resident>))
  464. end
  465. def test_one_level_with_nils
  466. xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options)
  467. assert_equal "<person>", xml.first(8)
  468. assert xml.include?(%(<street>Paulina</street>))
  469. assert xml.include?(%(<name>David</name>))
  470. assert xml.include?(%(<age nil="true"></age>))
  471. end
  472. def test_one_level_with_skipping_types
  473. xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options.merge(:skip_types => true))
  474. assert_equal "<person>", xml.first(8)
  475. assert xml.include?(%(<street>Paulina</street>))
  476. assert xml.include?(%(<name>David</name>))
  477. assert xml.include?(%(<age nil="true"></age>))
  478. end
  479. def test_one_level_with_yielding
  480. xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) do |x|
  481. x.creator("Rails")
  482. end
  483. assert_equal "<person>", xml.first(8)
  484. assert xml.include?(%(<street>Paulina</street>))
  485. assert xml.include?(%(<name>David</name>))
  486. assert xml.include?(%(<creator>Rails</creator>))
  487. end
  488. def test_two_levels
  489. xml = { :name => "David", :address => { :street => "Paulina" } }.to_xml(@xml_options)
  490. assert_equal "<person>", xml.first(8)
  491. assert xml.include?(%(<address><street>Paulina</street></address>))
  492. assert xml.include?(%(<name>David</name>))
  493. end
  494. def test_two_levels_with_second_level_overriding_to_xml
  495. xml = { :name => "David", :address => { :street => "Paulina" }, :child => IWriteMyOwnXML.new }.to_xml(@xml_options)
  496. assert_equal "<person>", xml.first(8)
  497. assert xml.include?(%(<address><street>Paulina</street></address>))
  498. assert xml.include?(%(<level_one><second_level>content</second_level></level_one>))
  499. end
  500. def test_two_levels_with_array
  501. xml = { :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] }.to_xml(@xml_options)
  502. assert_equal "<person>", xml.first(8)
  503. assert xml.include?(%(<addresses type="array"><address>))
  504. assert xml.include?(%(<address><street>Paulina</street></address>))
  505. assert xml.include?(%(<address><street>Evergreen</street></address>))
  506. assert xml.include?(%(<name>David</name>))
  507. end
  508. def test_three_levels_with_array
  509. xml = { :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] }.to_xml(@xml_options)
  510. assert xml.include?(%(<addresses type="array"><address><streets type="array"><street><name>))
  511. end
  512. def test_timezoned_attributes
  513. xml = {
  514. :created_at => Time.utc(1999,2,2),
  515. :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)')
  516. }.to_xml(@xml_options)
  517. assert_match %r{<created-at type=\"datetime\">1999-02-02T00:00:00Z</created-at>}, xml
  518. assert_match %r{<local-created-at type=\"datetime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml
  519. end
  520. def test_single_record_from_xml
  521. topic_xml = <<-EOT
  522. <topic>
  523. <title>The First Topic</title>
  524. <author-name>David</author-name>
  525. <id type="integer">1</id>
  526. <approved type="boolean"> true </approved>
  527. <replies-count type="integer">0</replies-count>
  528. <replies-close-in type="integer">2592000000</replies-close-in>
  529. <written-on type="date">2003-07-16</written-on>
  530. <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
  531. <author-email-address>david@loudthinking.com</author-email-address>
  532. <parent-id></parent-id>
  533. <ad-revenue type="decimal">1.5</ad-revenue>
  534. <optimum-viewing-angle type="float">135</optimum-viewing-angle>
  535. </topic>
  536. EOT
  537. expected_topic_hash = {
  538. :title => "The First Topic",
  539. :author_name => "David",
  540. :id => 1,
  541. :approved => true,
  542. :replies_count => 0,
  543. :replies_close_in => 2592000000,
  544. :written_on => Date.new(2003, 7, 16),
  545. :viewed_at => Time.utc(2003, 7, 16, 9, 28),
  546. :author_email_address => "david@loudthinking.com",
  547. :parent_id => nil,
  548. :ad_revenue => BigDecimal("1.50"),
  549. :optimum_viewing_angle => 135.0,
  550. }.stringify_keys
  551. assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
  552. end
  553. def test_single_record_from_xml_with_nil_values
  554. topic_xml = <<-EOT
  555. <topic>
  556. <title></title>
  557. <id type="integer"></id>
  558. <approved type="boolean"></approved>
  559. <written-on type="date"></written-on>
  560. <viewed-at type="datetime"></viewed-at>
  561. <parent-id></parent-id>
  562. </topic>
  563. EOT
  564. expected_topic_hash = {
  565. :title => nil,
  566. :id => nil,
  567. :approved => nil,
  568. :written_on => nil,
  569. :viewed_at => nil,
  570. :parent_id => nil
  571. }.stringify_keys
  572. assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"]
  573. end
  574. def test_multiple_records_from_xml
  575. topics_xml = <<-EOT
  576. <topics type="array">
  577. <topic>
  578. <title>The First Topic</title>
  579. <author-name>David</author-name>
  580. <id type="integer">1</id>
  581. <approved type="boolean">false</approved>
  582. <replies-count type="integer">0</replies-count>
  583. <replies-close-in type="integer">2592000000</replies-close-in>
  584. <written-on type="date">2003-07-16</written-on>
  585. <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
  586. <content>Have a nice day</content>
  587. <author-email-address>david@loudthinking.com</author-email-address>
  588. <parent-id nil="true"></parent-id>
  589. </topic>
  590. <topic>
  591. <title>The Second Topic</title>
  592. <author-name>Jason</author-name>
  593. <id type="integer">1</id>
  594. <approved type="boolean">false</approved>
  595. <replies-count type="integer">0</replies-count>
  596. <replies-close-in type="integer">2592000000</replies-close-in>
  597. <written-on type="date">2003-07-16</written-on>
  598. <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
  599. <content>Have a nice day</content>
  600. <author-email-address>david@loudthinking.com</author-email-address>
  601. <parent-id></parent-id>
  602. </topic>
  603. </topics>
  604. EOT
  605. expected_topic_hash = {
  606. :title => "The First Topic",
  607. :author_name => "David",
  608. :id => 1,
  609. :approved => false,
  610. :replies_count => 0,
  611. :replies_close_in => 2592000000,
  612. :written_on => Date.new(2003, 7, 16),
  613. :viewed_at => Time.utc(2003, 7, 16, 9, 28),
  614. :content => "Have a nice day",
  615. :author_email_address => "david@loudthinking.com",
  616. :parent_id => nil
  617. }.stringify_keys
  618. assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first
  619. end
  620. def test_single_record_from_xml_with_attributes_other_than_type
  621. topic_xml = <<-EOT
  622. <rsp stat="ok">
  623. <photos page="1" pages="1" perpage="100" total="16">
  624. <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
  625. </photos>
  626. </rsp>
  627. EOT
  628. expected_topic_hash = {
  629. :id => "175756086",
  630. :owner => "55569174@N00",
  631. :secret => "0279bf37a1",
  632. :server => "76",
  633. :title => "Colored Pencil PhotoBooth Fun",
  634. :ispublic => "1",
  635. :isfriend => "0",
  636. :isfamily => "0",
  637. }.stringify_keys
  638. assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"]
  639. end
  640. def test_all_caps_key_from_xml
  641. test_xml = <<-EOT
  642. <ABC3XYZ>
  643. <TEST>Lorem Ipsum</TEST>
  644. </ABC3XYZ>
  645. EOT
  646. expected_hash = {
  647. "ABC3XYZ" => {
  648. "TEST" => "Lorem Ipsum"
  649. }
  650. }
  651. assert_equal expected_hash, Hash.from_xml(test_xml)
  652. end
  653. def test_empty_array_from_xml
  654. blog_xml = <<-XML
  655. <blog>
  656. <posts type="array"></posts>
  657. </blog>
  658. XML
  659. expected_blog_hash = {"blog" => {"posts" => []}}
  660. assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  661. end
  662. def test_empty_array_with_whitespace_from_xml
  663. blog_xml = <<-XML
  664. <blog>
  665. <posts type="array">
  666. </posts>
  667. </blog>
  668. XML
  669. expected_blog_hash = {"blog" => {"posts" => []}}
  670. assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  671. end
  672. def test_array_with_one_entry_from_xml
  673. blog_xml = <<-XML
  674. <blog>
  675. <posts type="array">
  676. <post>a post</post>
  677. </posts>
  678. </blog>
  679. XML
  680. expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
  681. assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  682. end
  683. def test_array_with_multiple_entries_from_xml
  684. blog_xml = <<-XML
  685. <blog>
  686. <posts type="array">
  687. <post>a post</post>
  688. <post>another post</post>
  689. </posts>
  690. </blog>
  691. XML
  692. expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
  693. assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
  694. end
  695. def test_file_from_xml
  696. blog_xml = <<-XML
  697. <blog>
  698. <logo type="file" name="logo.png" content_type="image/png">
  699. </logo>
  700. </blog>
  701. XML
  702. hash = Hash.from_xml(blog_xml)
  703. assert hash.has_key?('blog')
  704. assert hash['blog'].has_key?('logo')
  705. file = hash['blog']['logo']
  706. assert_equal 'logo.png', file.original_filename
  707. assert_equal 'image/png', file.content_type
  708. end
  709. def test_file_from_xml_with_defaults
  710. blog_xml = <<-XML
  711. <blog>
  712. <logo type="file">
  713. </logo>
  714. </blog>
  715. XML
  716. file = Hash.from_xml(blog_xml)['blog']['logo']
  717. assert_equal 'untitled', file.original_filename
  718. assert_equal 'application/octet-stream', file.content_type
  719. end
  720. def test_tag_with_attrs_and_whitespace
  721. xml = <<-XML
  722. <blog name="bacon is the best">
  723. </blog>
  724. XML
  725. hash = Hash.from_xml(xml)
  726. assert_equal "bacon is the best", hash['blog']['name']
  727. end
  728. def test_empty_cdata_from_xml
  729. xml = "<data><![CDATA[]]></data>"
  730. assert_equal "", Hash.from_xml(xml)["data"]
  731. end
  732. def test_xsd_like_types_from_xml
  733. bacon_xml = <<-EOT
  734. <bacon>
  735. <weight type="double">0.5</weight>
  736. <price type="decimal">12.50</price>
  737. <chunky type="boolean"> 1 </chunky>
  738. <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
  739. <notes type="string"></notes>
  740. <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
  741. <caption type="binary" encoding="base64">VGhhdCdsbCBkbywgcGlnLg==</caption>
  742. </bacon>
  743. EOT
  744. expected_bacon_hash = {
  745. :weight => 0.5,
  746. :chunky => true,
  747. :price => BigDecimal("12.50"),
  748. :expires_at => Time.utc(2007,12,25,12,34,56),
  749. :notes => "",
  750. :illustration => "babe.png",
  751. :caption => "That'll do, pig."
  752. }.stringify_keys
  753. assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"]
  754. end
  755. def test_type_trickles_through_when_unknown
  756. product_xml = <<-EOT
  757. <product>
  758. <weight type="double">0.5</weight>
  759. <image type="ProductImage"><filename>image.gif</filename></image>
  760. </product>
  761. EOT
  762. expected_product_hash = {
  763. :weight => 0.5,
  764. :image => {'type' => 'ProductImage', 'filename' => 'image.gif' },
  765. }.stringify_keys
  766. assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
  767. end
  768. def test_from_xml_raises_on_disallowed_type_attributes
  769. assert_raise Hash::DisallowedType do
  770. Hash.from_xml '<product><name type="foo">value</name></product>', %w(foo)
  771. end
  772. end
  773. def test_from_xml_disallows_symbol_and_yaml_types_by_default
  774. assert_raise Hash::DisallowedType do
  775. Hash.from_xml '<product><name type="symbol">value</name></product>'
  776. end
  777. assert_raise Hash::DisallowedType do
  778. Hash.from_xml '<product><name type="yaml">value</name></product>'
  779. end
  780. end
  781. def test_from_trusted_xml_allows_symbol_and_yaml_types
  782. expected = { 'product' => { 'name' => :value }}
  783. assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>')
  784. assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
  785. end
  786. def test_should_use_default_value_for_unknown_key
  787. hash_wia = HashWithIndifferentAccess.new(3)
  788. assert_equal 3, hash_wia[:new_key]
  789. end
  790. def test_should_use_default_value_if_no_key_is_supplied
  791. hash_wia = HashWithIndifferentAccess.new(3)
  792. assert_equal 3, hash_wia.default
  793. end
  794. def test_should_nil_if_no_default_value_is_supplied
  795. hash_wia = HashWithIndifferentAccess.new
  796. assert_nil hash_wia.default
  797. end
  798. def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
  799. hash = Hash.new(3)
  800. hash_wia = hash.with_indifferent_access
  801. assert_equal 3, hash_wia.default
  802. end
  803. # The XML builder seems to fail miserably when trying to tag something
  804. # with the same name as a Kernel method (throw, test, loop, select ...)
  805. def test_kernel_method_names_to_xml
  806. hash = { :throw => { :ball => 'red' } }
  807. expected = '<person><throw><ball>red</ball></throw></person>'
  808. assert_nothing_raised do
  809. assert_equal expected, hash.to_xml(@xml_options)
  810. end
  811. end
  812. def test_empty_string_works_for_typecast_xml_value
  813. assert_nothing_raised do
  814. Hash.__send__(:typecast_xml_value, "")
  815. end
  816. end
  817. def test_escaping_to_xml
  818. hash = {
  819. :bare_string => 'First & Last Name',
  820. :pre_escaped_string => 'First &amp; Last Name'
  821. }.stringify_keys
  822. assert hash.to_xml(@xml_options).include?("<bare-string>First &amp; Last Name</bare-string>")
  823. assert hash.to_xml(@xml_options).include?("<pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string>")
  824. end
  825. def test_unescaping_from_xml
  826. xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
  827. expected_hash = {
  828. :bare_string => 'First & Last Name',
  829. :pre_escaped_string => 'First &amp; Last Name'
  830. }.stringify_keys
  831. assert_equal expected_hash, Hash.from_xml(xml_string)['person']
  832. end
  833. def test_roundtrip_to_xml_from_xml
  834. hash = {
  835. :bare_string => 'First & Last Name',
  836. :pre_escaped_string => 'First &amp; Last Name'
  837. }.stringify_keys
  838. assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person']
  839. end
  840. def test_datetime_xml_type_with_utc_time
  841. alert_xml = <<-XML
  842. <alert>
  843. <alert_at type="datetime">2008-02-10T15:30:45Z</alert_at>
  844. </alert>
  845. XML
  846. alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
  847. assert alert_at.utc?
  848. assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
  849. end
  850. def test_datetime_xml_type_with_non_utc_time
  851. alert_xml = <<-XML
  852. <alert>
  853. <alert_at type="datetime">2008-02-10T10:30:45-05:00</alert_at>
  854. </alert>
  855. XML
  856. alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
  857. assert alert_at.utc?
  858. assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
  859. end
  860. def test_datetime_xml_type_with_far_future_date
  861. alert_xml = <<-XML
  862. <alert>
  863. <alert_at type="datetime">2050-02-10T15:30:45Z</alert_at>
  864. </alert>
  865. XML
  866. alert_at = Hash.from_xml(alert_xml)['alert']['alert_at']
  867. assert alert_at.utc?
  868. assert_equal 2050, alert_at.year
  869. assert_equal 2, alert_at.month
  870. assert_equal 10, alert_at.day
  871. assert_equal 15, alert_at.hour
  872. assert_equal 30, alert_at.min
  873. assert_equal 45, alert_at.sec
  874. end
  875. def test_to_xml_dups_options
  876. options = {:skip_instruct => true}
  877. {}.to_xml(options)
  878. # :builder, etc, shouldn't be added to options
  879. assert_equal({:skip_instruct => true}, options)
  880. end
  881. def test_expansion_count_is_limited
  882. expected =
  883. case ActiveSupport::XmlMini.backend.name
  884. when 'ActiveSupport::XmlMini_REXML'; RuntimeError
  885. when 'ActiveSupport::XmlMini_Nokogiri'; Nokogiri::XML::SyntaxError
  886. when 'ActiveSupport::XmlMini_NokogiriSAX'; RuntimeError
  887. when 'ActiveSupport::XmlMini_LibXML'; LibXML::XML::Error
  888. when 'ActiveSupport::XmlMini_LibXMLSAX'; LibXML::XML::Error
  889. end
  890. assert_raise expected do
  891. attack_xml = <<-EOT
  892. <?xml version="1.0" encoding="UTF-8"?>
  893. <!DOCTYPE member [
  894. <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
  895. <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
  896. <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
  897. <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
  898. <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
  899. <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
  900. <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
  901. ]>
  902. <member>
  903. &a;
  904. </member>
  905. EOT
  906. Hash.from_xml(attack_xml)
  907. end
  908. end
  909. end