Skip to content

Commit

Permalink
Add bidirectional converter specs to test namespaces
Browse files Browse the repository at this point in the history
Previously, we might have duplicated namespaces when converting the same string back and forth. With the latest change in #52, we're avoiding this error and are now adding respective test coverage for this phenomena.
  • Loading branch information
MrSerth committed Oct 9, 2023
1 parent e21cc91 commit 2aad5e9
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 0 deletions.
98 changes: 98 additions & 0 deletions spec/custom_matchers/equal_namespace_definitions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

RSpec.describe 'equal_namespace_definitions matcher' do
let(:xml) { Examples::Example2.xml }
let(:xml2) { Examples::Example2.xml }

it 'successfully compares two similar xml strings' do
expect(xml).to have_equal_namespace_definitions_as xml2
end

context 'when one of the classes is not a string' do
let(:string) { 'hello' }
let(:integer) { 1234 }

it 'fails' do
expect(string).not_to have_equal_namespace_definitions_as integer
end

it 'provides a useful error message' do
expect { expect(string).to have_equal_namespace_definitions_as integer }
.to raise_error(RSpec::Expectations::ExpectationNotMetError, /does not have equal namespaces as/)
end
end

context 'when one of the strings does not contain valid xml' do
let(:xml2) { 'foobar' }

it 'fails' do
expect(xml).not_to have_equal_namespace_definitions_as xml2
end

it 'provides a useful error message' do
expect { expect(xml).to have_equal_namespace_definitions_as xml2 }
.to raise_error(RSpec::Expectations::ExpectationNotMetError, /does not have equal namespaces as/)
end
end

context 'when one xml string is different' do
let(:xml2) { Examples::Example3.json }

it 'fails the comparison' do
expect(xml).not_to have_equal_namespace_definitions_as xml2
end

it 'provides a useful error message' do
expect { expect(xml).to have_equal_namespace_definitions_as xml2 }
.to raise_error(RSpec::Expectations::ExpectationNotMetError, /does not have equal namespaces as/)
end
end

context 'with namespaces' do
let(:xml) do
<<-XML
<alice xmlns="http://some-namespace" xmlns:charlie="http://some-other-namespace">
<bob>david</bob>
<charlie:edgar>frank</charlie:edgar>
</alice>
XML
end

context 'with unequal namespace definitions' do
let(:xml2) do
<<~XML
<alice xmlns:charlie="http://some-other-namespace" xmlns="http://some-namespace">
<bob>david</bob>
<charlie:edgar xmlns:charlie="http://some-other-namespace" xmlns="http://some-namespace">
frank
</charlie:edgar>
</alice>
XML
end

it 'fails the comparison' do
expect(xml).not_to have_equal_namespace_definitions_as xml2
end

it 'provides a useful error message' do
expect { expect(xml).to have_equal_namespace_definitions_as xml2 }
.to raise_error(RSpec::Expectations::ExpectationNotMetError, /does not have equal namespaces as/)
end
end

context 'with similar namespace definitions' do
let(:xml2) do
<<~XML
<alice xmlns:charlie="http://some-other-namespace" xmlns="http://some-namespace">
<bob>david</bob>
<charlie:edgar>frank</charlie:edgar>
</alice>
XML
end

it 'successfully compares two similar xml strings' do
expect(xml).to have_equal_namespace_definitions_as xml2
end
end
end
end
39 changes: 39 additions & 0 deletions spec/dachsfisch/bidirectional_converter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

RSpec.describe 'BidirectionalConverter' do
let(:xml2_json) { Dachsfisch::XML2JSONConverter }
let(:json2_xml) { Dachsfisch::JSON2XMLConverter }

context 'with valid XML' do
describe '#perform' do
subject { json2_xml.perform json: }

let(:json) { xml2_json.perform xml: }

Examples.each :json2xml do |example|
context "with #{example.name}" do
let(:xml) { example.xml }

it { is_expected.to be_equivalent_to(xml) }
it { is_expected.to have_equal_namespace_definitions_as(xml) }
end
end
end
end

context 'with valid JSON' do
describe '#perform' do
subject { xml2_json.perform xml: }

let(:xml) { json2_xml.perform json: }

Examples.each :xml2json do |example|
context "with #{example.name}" do
let(:json) { example.json }

it { is_expected.to be_an_equal_json_as(json) }
end
end
end
end
end
59 changes: 59 additions & 0 deletions spec/support/expectations/equal_namespace_definitions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

require 'rspec/expectations'

RSpec::Matchers.define :have_equal_namespace_definitions_as do |expected|
attr_reader :actual, :expected

match do |actual|
return false unless actual.is_a?(String) && expected.is_a?(String)

expected_xml = parse_fragment(expected)
@expected = expected_xml.to_xml
actual_xml = parse_fragment(actual)
@actual = actual_xml.to_xml

return false if expected_xml.errors.length.positive? || actual_xml.errors.length.positive?
return false unless EquivalentXml.equivalent?(expected_xml, actual_xml)

return compare_namespaces(actual_xml.children.first, expected_xml.children.first)
end

failure_message do |actual|
"#{@actual || actual} does not have equal namespaces as \n#{@expected || expected}."
end

diffable

private

def compare_namespaces(actual, expected)
return false unless same_namespace_definitions?(actual, expected)

actual.children.each_with_index.all? do |actual_child, index|
expected_child = expected.children[index]
compare_namespaces(actual_child, expected_child)
end
end

def same_namespace_definitions?(actual, expected)
return true if actual.nil? && expected.nil?
return false if actual.nil? || expected.nil?

actual_namespaces = namespaces(actual)
expected_namespaces = namespaces(expected)
actual_namespaces == expected_namespaces
end

def namespaces(node)
node.namespace_definitions.map {|namespace| namespace.deconstruct_keys(%i[prefix href]) }.sort_by {|ns| ns[:prefix].to_s }
end

def parse_fragment(xml)
# This is a workaround for an unintended behavior in Nokogiri's XML::DocumentFragment.parse method.
# Originally, the method de-duplicates the namespace definitions of all nodes in the fragment, if possible.
# However, for this test, we want to compare the namespace definitions of the actual and expected XML.
# Therefore, we parse the XML with a root node, and compare the resulting document.
Nokogiri::XML::Document.parse("<root>#{xml}</root>", &:noblanks)
end
end

0 comments on commit 2aad5e9

Please sign in to comment.