1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
|
require "test_helper"
require "support/mock_decoder"
# Tests setting and retrieving the global XML parser backend
class MultiXmlParserConfigTest < Minitest::Test
cover "MultiXml*"
def setup
@original_parser = MultiXml.instance_variable_get(:@parser)
end
def teardown
if @original_parser
MultiXml.instance_variable_set(:@parser, @original_parser)
elsif MultiXml.instance_variable_defined?(:@parser)
MultiXml.send(:remove_instance_variable, :@parser)
end
end
def test_picks_a_default_parser
parser = MultiXml.parser
assert_kind_of Module, parser
assert_respond_to parser, :parse
end
def test_defaults_to_the_best_available_gem
MultiXml.send(:remove_instance_variable, :@parser) if MultiXml.instance_variable_defined?(:@parser)
expected = (windows? || jruby?) ? "MultiXml::Parsers::Nokogiri" : "MultiXml::Parsers::Ox"
assert_equal expected, MultiXml.parser.name
end
def test_is_settable_via_a_symbol
MultiXml.parser = :rexml
assert_equal "MultiXml::Parsers::Rexml", MultiXml.parser.name
end
def test_is_settable_via_a_class
MultiXml.parser = MockDecoder
assert_equal "MockDecoder", MultiXml.parser.name
end
end
# Tests overriding the parser on a per-call basis via the :parser option
class MultiXmlPerParseParserTest < Minitest::Test
cover "MultiXml*"
def setup
@original_parser = MultiXml.instance_variable_get(:@parser)
end
def teardown
if @original_parser
MultiXml.instance_variable_set(:@parser, @original_parser)
elsif MultiXml.instance_variable_defined?(:@parser)
MultiXml.send(:remove_instance_variable, :@parser)
end
end
def test_allows_per_parse_parser_via_symbol
MultiXml.parser = :rexml
assert_equal({"user" => "Erik"}, MultiXml.parse("<user>Erik</user>", parser: :nokogiri))
end
def test_allows_per_parse_parser_via_string
MultiXml.parser = :rexml
assert_equal({"user" => "Erik"}, MultiXml.parse("<user>Erik</user>", parser: "nokogiri"))
end
def test_allows_per_parse_parser_via_class
MultiXml.parser = :rexml
require "multi_xml/parsers/nokogiri"
assert_equal({"user" => "Erik"}, MultiXml.parse("<user>Erik</user>", parser: MultiXml::Parsers::Nokogiri))
end
def test_does_not_change_class_level_parser_when_using_per_parse_parser
MultiXml.parser = :rexml
MultiXml.parse("<user>Erik</user>", parser: :nokogiri)
assert_equal "MultiXml::Parsers::Rexml", MultiXml.parser.name
end
def test_uses_class_level_parser_when_parser_option_is_not_provided
MultiXml.parser = :nokogiri
result = MultiXml.parse("<user>Erik</user>")
assert_equal({"user" => "Erik"}, result)
end
def test_raises_error_for_invalid_per_parse_parser
error = assert_raises(RuntimeError) { MultiXml.parse("<user/>", parser: 123) }
assert_match(/Invalid parser specification/, error.message)
end
def test_wraps_parser_errors_correctly_with_per_parse_parser
assert_raises(MultiXml::ParseError) { MultiXml.parse("<open></close>", parser: :nokogiri) }
end
def test_options_parser_key_is_truthy_when_present
result = MultiXml.parse("<root>test</root>", parser: :nokogiri)
assert_equal({"root" => "test"}, result)
end
def test_options_without_parser_uses_default
MultiXml.parser = :rexml
result = MultiXml.parse("<root>test</root>")
assert_equal({"root" => "test"}, result)
end
end
# Tests automatic type conversion based on XML type attributes (float, binary, datetime, etc.)
class MultiXmlTypecastTest < Minitest::Test
cover "MultiXml*"
def setup
@original_parser = MultiXml.instance_variable_get(:@parser)
end
def teardown
if @original_parser
MultiXml.instance_variable_set(:@parser, @original_parser)
elsif MultiXml.instance_variable_defined?(:@parser)
MultiXml.send(:remove_instance_variable, :@parser)
end
end
def test_float_type_returns_float
MultiXml.parser = best_available_parser
result = MultiXml.parse('<tag type="float">3.14</tag>')["tag"]
assert_kind_of Float, result
assert_in_delta(3.14, result)
end
def test_string_type_with_content_returns_string
MultiXml.parser = best_available_parser
result = MultiXml.parse('<tag type="string">hello</tag>')["tag"]
assert_kind_of String, result
assert_equal "hello", result
end
def test_binary_type_with_base64_encoding_decodes_content
MultiXml.parser = best_available_parser
result = MultiXml.parse('<tag type="binary" encoding="base64">ZGF0YQ==</tag>')["tag"]
assert_equal "data", result
end
def test_binary_type_without_encoding_returns_raw_content
MultiXml.parser = best_available_parser
result = MultiXml.parse('<tag type="binary">raw data</tag>')["tag"]
assert_equal "raw data", result
end
def test_datetime_fallback_to_datetime_class
MultiXml.parser = best_available_parser
result = MultiXml.parse('<tag type="datetime">1970-01-01T00:00:00+00:00</tag>')["tag"]
assert_kind_of Time, result
end
def test_invalid_yaml_returns_original_string
MultiXml.parser = best_available_parser
xml = '<tag type="yaml">{ invalid yaml content</tag>'
result = MultiXml.parse(xml, disallowed_types: [])["tag"]
assert_equal "{ invalid yaml content", result
end
def test_three_sibling_elements_creates_array
MultiXml.parser = best_available_parser
xml = "<users><user>A</user><user>B</user><user>C</user></users>"
result = MultiXml.parse(xml)["users"]["user"]
assert_kind_of Array, result
assert_equal %w[A B C], result
end
end
# Tests for empty input handling
class MultiXmlEmptyInputTest < Minitest::Test
cover "MultiXml*"
def test_parse_empty_string_returns_empty_hash
result = MultiXml.parse("")
assert_empty(result)
end
def test_parse_empty_xml_returns_empty_hash_not_nil
result = MultiXml.parse(" ")
assert_empty(result)
refute_nil result
end
def test_parse_empty_input_early_returns
result = MultiXml.parse("")
assert_empty(result)
end
end
# Tests conversion of dashed XML element names to underscored Ruby hash keys
class MultiXmlKeyTransformTest < Minitest::Test
cover "MultiXml*"
def setup
@original_parser = MultiXml.instance_variable_get(:@parser)
MultiXml.parser = best_available_parser
end
def teardown
if @original_parser
MultiXml.instance_variable_set(:@parser, @original_parser)
elsif MultiXml.instance_variable_defined?(:@parser)
MultiXml.send(:remove_instance_variable, :@parser)
end
end
def test_parse_with_error_handling_undasherizes_keys
result = MultiXml.parse("<root><my-key>value</my-key></root>")
assert_equal({"root" => {"my_key" => "value"}}, result)
refute result["root"].key?("my-key")
assert result["root"].key?("my_key")
end
end
# Tests that malformed XML raises ParseError with a meaningful message
class MultiXmlParseErrorTest < Minitest::Test
cover "MultiXml*"
def setup
@original_parser = MultiXml.instance_variable_get(:@parser)
end
def teardown
if @original_parser
MultiXml.instance_variable_set(:@parser, @original_parser)
elsif MultiXml.instance_variable_defined?(:@parser)
MultiXml.send(:remove_instance_variable, :@parser)
end
end
def test_parse_error_message_is_string
MultiXml.parser = :nokogiri
error = assert_raises(MultiXml::ParseError) do
MultiXml.parse("<open></close>")
end
assert_kind_of String, error.message
refute_match(/REXML::ParseException/, error.message) if error.message.is_a?(String)
end
end
# Tests for parser loading
class MultiXmlParserLoadingTest < Minitest::Test
cover "MultiXml*"
def test_load_parser_with_mixed_case_name
parser = MultiXml.send(:load_parser, "Nokogiri")
assert_equal "MultiXml::Parsers::Nokogiri", parser.name
end
def test_load_parser_with_symbol
parser = MultiXml.send(:load_parser, :NOKOGIRI)
assert_equal "MultiXml::Parsers::Nokogiri", parser.name
end
def test_find_loaded_parser_uses_object_const_defined
result = MultiXml.send(:find_loaded_parser)
assert_find_loaded_parser_result(result)
end
def test_resolve_parser_with_class
require "multi_xml/parsers/nokogiri"
parser = MultiXml.send(:resolve_parser, MultiXml::Parsers::Nokogiri)
assert_equal MultiXml::Parsers::Nokogiri, parser
end
private
def assert_find_loaded_parser_result(result)
expected = expected_loaded_parser
expected ? assert_equal(expected, result) : assert_nil(result)
end
def expected_loaded_parser
return :ox if defined?(Ox)
return :libxml if defined?(LibXML)
return :nokogiri if defined?(Nokogiri)
:oga if defined?(Oga)
end
end
|