PageRenderTime 52ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/activesupport/test/core_ext/hash_ext_test.rb

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