PageRenderTime 31ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/Languages/Ruby/Tests/Libraries/Rails-3.0.0/activesupport/test/core_ext/hash_ext_test.rb

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