diff --git a/.gitignore b/.gitignore index 3348a20..7ba7fcf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ +# Files generated by interlis-testbed-runner +output/ + ### IntelliJ IDEA ### .idea/modules.xml .idea/jarRepositories.xml diff --git a/build.gradle b/build.gradle index 6f4e828..26b6967 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ dependencies { testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.xmlunit:xmlunit-core:2.9.1' } application { diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/Basket.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/Basket.java new file mode 100644 index 0000000..713487b --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/Basket.java @@ -0,0 +1,46 @@ +package ch.geowerkstatt.interlis.testbed.runner.xtf; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.Map; + +public record Basket(Element element, Map objects) { + /** + * Adds or replaces the child node with the given entry ID. + * + * @param entryId the entry ID + * @param node the node to add or replace + */ + public void addOrReplaceChildNode(String entryId, Node node) { + var originalEntry = objects().get(entryId); + if (originalEntry == null) { + element().appendChild(node); + } else { + element().replaceChild(node, originalEntry); + } + } + + /** + * Removes the basket node from the XML document. + */ + public void removeBasketNode() { + element().getParentNode().removeChild(element()); + } + + /** + * Removes the child node with the given entry ID. + * + * @param entryId the ID of the entry to remove + * @return {@code true} if the entry was removed, {@code false} if the entry was not found + */ + public boolean removeChildNode(String entryId) { + var originalEntry = objects().get(entryId); + if (originalEntry == null) { + return false; + } + + element().removeChild(originalEntry); + return true; + } +} diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfFileMerger.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfFileMerger.java new file mode 100644 index 0000000..95018a8 --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfFileMerger.java @@ -0,0 +1,194 @@ +package ch.geowerkstatt.interlis.testbed.runner.xtf; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public final class XtfFileMerger implements XtfMerger { + private static final Logger LOGGER = LogManager.getLogger(); + private static final String BASKET_ID = "BID"; + private static final String OBJECT_ID = "TID"; + private static final String DELETE_ATTRIBUTE = "DELETE"; + private static final String DELETE_ATTRIBUTE_LOWERCASE = DELETE_ATTRIBUTE.toLowerCase(); + private static final String INTERLIS24_NAMESPACE = "http://www.interlis.ch/xtf/2.4/INTERLIS"; + + private final DocumentBuilderFactory factory; + + /** + * Creates a new instance of the XtfFileMerger class. + */ + public XtfFileMerger() { + factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + } + + @Override + public boolean merge(Path baseFile, Path patchFile, Path outputFile) { + try { + LOGGER.info("Merging " + baseFile + " with " + patchFile + " into " + outputFile); + var documentBuilder = createDocumentBuilder(); + + var baseDocument = documentBuilder.parse(baseFile.toFile()); + var patchDocument = documentBuilder.parse(patchFile.toFile()); + + var baseBaskets = findBaskets(baseDocument); + if (baseBaskets.isEmpty()) { + LOGGER.error("No baskets found in base file " + baseFile + "."); + return false; + } + + var patchBaskets = findBaskets(patchDocument); + if (patchBaskets.isEmpty()) { + LOGGER.error("No baskets found in patch file " + patchFile + "."); + return false; + } + + if (!mergeBaskets(baseDocument, baseBaskets.get(), patchBaskets.get())) { + return false; + } + + writeMergedFile(baseDocument, outputFile); + LOGGER.info("Successfully merged files into " + outputFile); + return true; + } catch (Exception e) { + LOGGER.error("Failed to merge files.", e); + return false; + } + } + + DocumentBuilder createDocumentBuilder() throws ParserConfigurationException { + return factory.newDocumentBuilder(); + } + + private static boolean mergeBaskets(Document document, Map baseBaskets, Map patchBaskets) { + var isValid = true; + + for (var patchBasketEntry : patchBaskets.entrySet()) { + var basketId = patchBasketEntry.getKey(); + var patchBasket = patchBasketEntry.getValue(); + + var originalBasket = baseBaskets.get(basketId); + if (originalBasket == null) { + LOGGER.error("Basket " + basketId + " not found in base file."); + isValid = false; + continue; + } + + if (hasDeleteAttribute(patchBasket.element())) { + originalBasket.removeBasketNode(); + continue; + } + + for (var patchEntry : patchBasket.objects().entrySet()) { + var entryId = patchEntry.getKey(); + var element = patchEntry.getValue(); + + if (hasDeleteAttribute(element)) { + if (!originalBasket.removeChildNode(entryId)) { + LOGGER.error("Could not remove entry " + entryId + " from basket " + basketId + " as it does not exist."); + isValid = false; + } + } else { + var importedNode = document.importNode(element, true); + originalBasket.addOrReplaceChildNode(entryId, importedNode); + } + } + } + + return isValid; + } + + private static void writeMergedFile(Document document, Path outputFile) throws IOException, TransformerException { + Files.createDirectories(outputFile.getParent()); + + var transformerFactory = TransformerFactory.newInstance(); + var transformer = transformerFactory.newTransformer(); + var source = new DOMSource(document); + var result = new StreamResult(outputFile.toFile()); + transformer.transform(source, result); + } + + static Optional> findBaskets(Document document) { + var dataSection = findDataSection(document); + if (dataSection.isEmpty()) { + return Optional.empty(); + } + + var baskets = streamChildElementNodes(dataSection.get()) + .filter(e -> { + var hasId = hasInterlisAttribute(e, BASKET_ID); + if (!hasId) { + LOGGER.warn("Basket without " + BASKET_ID + " found."); + } + return hasId; + }) + .collect(Collectors.toMap(e -> getInterlisAttribute(e, BASKET_ID), XtfFileMerger::collectBasket)); + return Optional.of(baskets); + } + + private static Basket collectBasket(Element basket) { + var objects = streamChildElementNodes(basket) + .filter(e -> { + var hasId = hasInterlisAttribute(e, OBJECT_ID); + if (!hasId) { + LOGGER.warn("Entry without " + OBJECT_ID + " found in basket " + basket.getAttribute(BASKET_ID) + "."); + } + return hasId; + }) + .collect(Collectors.toMap(e -> getInterlisAttribute(e, OBJECT_ID), e -> e)); + return new Basket(basket, objects); + } + + private static boolean hasDeleteAttribute(Element element) { + return element.hasAttribute(DELETE_ATTRIBUTE) || element.hasAttribute(DELETE_ATTRIBUTE_LOWERCASE); + } + + private static boolean hasInterlisAttribute(Element element, String attributeName) { + return getInterlisAttribute(element, attributeName) != null; + } + + private static String getInterlisAttribute(Element element, String attributeName) { + if (element.hasAttribute(attributeName)) { + return element.getAttribute(attributeName); + } + + var ili24Name = attributeName.toLowerCase(); + if (element.hasAttributeNS(INTERLIS24_NAMESPACE, ili24Name)) { + return element.getAttributeNS(INTERLIS24_NAMESPACE, ili24Name); + } + return null; + } + + private static Optional findDataSection(Document document) { + var transfer = document.getFirstChild(); + return streamChildElementNodes(transfer) + .filter(n -> n.getLocalName().equalsIgnoreCase("datasection")) + .findFirst(); + } + + private static Stream streamChildElementNodes(Node node) { + var childNodes = node.getChildNodes(); + return IntStream.range(0, childNodes.getLength()) + .mapToObj(childNodes::item) + .filter(n -> n instanceof Element) + .map(n -> (Element) n); + } +} diff --git a/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfMerger.java b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfMerger.java new file mode 100644 index 0000000..189252b --- /dev/null +++ b/src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfMerger.java @@ -0,0 +1,15 @@ +package ch.geowerkstatt.interlis.testbed.runner.xtf; + +import java.nio.file.Path; + +public interface XtfMerger { + /** + * Merges the patch file into the base file and writes the result to the output file. + * + * @param baseFile the base file + * @param patchFile the patch file + * @param outputFile the output file + * @return {@code true} if the merge was successful, {@code false} otherwise. + */ + boolean merge(Path baseFile, Path patchFile, Path outputFile); +} diff --git a/src/test/data/xtf-merger/ili23/add/data.xtf b/src/test/data/xtf-merger/ili23/add/data.xtf new file mode 100644 index 0000000..a36bbe4 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/add/data.xtf @@ -0,0 +1,25 @@ + + + + + + + + + + Some text + Some more text + + + + + + Some text + Some more text + + + + + + + diff --git a/src/test/data/xtf-merger/ili23/add/expected.xtf b/src/test/data/xtf-merger/ili23/add/expected.xtf new file mode 100644 index 0000000..1639735 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/add/expected.xtf @@ -0,0 +1,33 @@ + + + + + + + + + + Some text + Some more text + + + New entry + Attr2 + + + + + + Some text + Some more text + + + + + + Attribute 1 + Attribute 2 + + + + diff --git a/src/test/data/xtf-merger/ili23/add/patch.xtf b/src/test/data/xtf-merger/ili23/add/patch.xtf new file mode 100644 index 0000000..7449ab4 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/add/patch.xtf @@ -0,0 +1,18 @@ + + + + + + New entry + Attr2 + + + + + + Attribute 1 + Attribute 2 + + + + diff --git a/src/test/data/xtf-merger/ili23/combined/data.xtf b/src/test/data/xtf-merger/ili23/combined/data.xtf new file mode 100644 index 0000000..de9d3af --- /dev/null +++ b/src/test/data/xtf-merger/ili23/combined/data.xtf @@ -0,0 +1,38 @@ + + + + + + + + + + Some text + Some more text + + + + 1.0 + 2.0 + + + 1.5 + 2.5 + + + + + + Some text + Some more text + + + + + + Some text + Some more text + + + + diff --git a/src/test/data/xtf-merger/ili23/combined/expected.xtf b/src/test/data/xtf-merger/ili23/combined/expected.xtf new file mode 100644 index 0000000..a86bf89 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/combined/expected.xtf @@ -0,0 +1,50 @@ + + + + + + + + + + Some text + Some more text + + + + 1.0 + 2.0 + + + 1.5 + 2.5 + + + + + + New value for attr1 + + + + + 10.0 + 20.0 + + + 100.0 + 20.0 + + + + + + New entry + Attr2 + + + + + + + diff --git a/src/test/data/xtf-merger/ili23/combined/patch.xtf b/src/test/data/xtf-merger/ili23/combined/patch.xtf new file mode 100644 index 0000000..7ce5439 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/combined/patch.xtf @@ -0,0 +1,31 @@ + + + + + + New value for attr1 + + + + + 10.0 + 20.0 + + + 100.0 + 20.0 + + + + + + New entry + Attr2 + + + + + + + + diff --git a/src/test/data/xtf-merger/ili23/delete/data.xtf b/src/test/data/xtf-merger/ili23/delete/data.xtf new file mode 100644 index 0000000..999c589 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/delete/data.xtf @@ -0,0 +1,26 @@ + + + + + + + + + + Some text + Some more text + + + Attribute 1 + Attribute 2 + + + + + + Attribute 1 + Attribute 2 + + + + diff --git a/src/test/data/xtf-merger/ili23/delete/expected.xtf b/src/test/data/xtf-merger/ili23/delete/expected.xtf new file mode 100644 index 0000000..08f1555 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/delete/expected.xtf @@ -0,0 +1,15 @@ + + + + + + + + + + Some text + Some more text + + + + diff --git a/src/test/data/xtf-merger/ili23/delete/patch.xtf b/src/test/data/xtf-merger/ili23/delete/patch.xtf new file mode 100644 index 0000000..500eb0b --- /dev/null +++ b/src/test/data/xtf-merger/ili23/delete/patch.xtf @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/data/xtf-merger/ili23/replace/data.xtf b/src/test/data/xtf-merger/ili23/replace/data.xtf new file mode 100644 index 0000000..e4b3c4a --- /dev/null +++ b/src/test/data/xtf-merger/ili23/replace/data.xtf @@ -0,0 +1,22 @@ + + + + + + + + + + Some text + Some more text + + + + + + Attribute 1 + Attribute 2 + + + + diff --git a/src/test/data/xtf-merger/ili23/replace/expected.xtf b/src/test/data/xtf-merger/ili23/replace/expected.xtf new file mode 100644 index 0000000..54126e8 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/replace/expected.xtf @@ -0,0 +1,23 @@ + + + + + + + + + + Some text + Some more text + + + + + + Replaced Value + + New attribute + + + + diff --git a/src/test/data/xtf-merger/ili23/replace/patch.xtf b/src/test/data/xtf-merger/ili23/replace/patch.xtf new file mode 100644 index 0000000..aedbfc9 --- /dev/null +++ b/src/test/data/xtf-merger/ili23/replace/patch.xtf @@ -0,0 +1,12 @@ + + + + + + Replaced Value + + New attribute + + + + diff --git a/src/test/data/xtf-merger/ili24/add/data.xtf b/src/test/data/xtf-merger/ili24/add/data.xtf new file mode 100644 index 0000000..b7000de --- /dev/null +++ b/src/test/data/xtf-merger/ili24/add/data.xtf @@ -0,0 +1,26 @@ + + + + + + interlis-testbed-runner + + + + + Some text + Some more text + + + + + + Some text + Some more text + + + + + + + diff --git a/src/test/data/xtf-merger/ili24/add/expected.xtf b/src/test/data/xtf-merger/ili24/add/expected.xtf new file mode 100644 index 0000000..69717a1 --- /dev/null +++ b/src/test/data/xtf-merger/ili24/add/expected.xtf @@ -0,0 +1,34 @@ + + + + + + interlis-testbed-runner + + + + + Some text + Some more text + + + New entry + Attr2 + + + + + + Some text + Some more text + + + + + + Attribute 1 + Attribute 2 + + + + \ No newline at end of file diff --git a/src/test/data/xtf-merger/ili24/add/patch.xtf b/src/test/data/xtf-merger/ili24/add/patch.xtf new file mode 100644 index 0000000..c3f38af --- /dev/null +++ b/src/test/data/xtf-merger/ili24/add/patch.xtf @@ -0,0 +1,18 @@ + + + + + + New entry + Attr2 + + + + + + Attribute 1 + Attribute 2 + + + + diff --git a/src/test/data/xtf-merger/ili24/combined/data.xtf b/src/test/data/xtf-merger/ili24/combined/data.xtf new file mode 100644 index 0000000..91cd39b --- /dev/null +++ b/src/test/data/xtf-merger/ili24/combined/data.xtf @@ -0,0 +1,39 @@ + + + + + + interlis-testbed-runner + + + + + Some text + Some more text + + + + 1.0 + 2.0 + + + 1.5 + 2.5 + + + + + + Some text + Some more text + + + + + + Some text + Some more text + + + + diff --git a/src/test/data/xtf-merger/ili24/combined/expected.xtf b/src/test/data/xtf-merger/ili24/combined/expected.xtf new file mode 100644 index 0000000..64efa27 --- /dev/null +++ b/src/test/data/xtf-merger/ili24/combined/expected.xtf @@ -0,0 +1,51 @@ + + + + + + interlis-testbed-runner + + + + + Some text + Some more text + + + + 1.0 + 2.0 + + + 1.5 + 2.5 + + + + + + New value for attr1 + + + + + 10.0 + 20.0 + + + 100.0 + 20.0 + + + + + + New entry + Attr2 + + + + + + + diff --git a/src/test/data/xtf-merger/ili24/combined/patch.xtf b/src/test/data/xtf-merger/ili24/combined/patch.xtf new file mode 100644 index 0000000..00e0884 --- /dev/null +++ b/src/test/data/xtf-merger/ili24/combined/patch.xtf @@ -0,0 +1,31 @@ + + + + + + New value for attr1 + + + + + 10.0 + 20.0 + + + 100.0 + 20.0 + + + + + + New entry + Attr2 + + + + + + + + diff --git a/src/test/data/xtf-merger/ili24/delete/data.xtf b/src/test/data/xtf-merger/ili24/delete/data.xtf new file mode 100644 index 0000000..684f786 --- /dev/null +++ b/src/test/data/xtf-merger/ili24/delete/data.xtf @@ -0,0 +1,27 @@ + + + + + + interlis-testbed-runner + + + + + Some text + Some more text + + + Attribute 1 + Attribute 2 + + + + + + Attribute 1 + Attribute 2 + + + + diff --git a/src/test/data/xtf-merger/ili24/delete/expected.xtf b/src/test/data/xtf-merger/ili24/delete/expected.xtf new file mode 100644 index 0000000..ae15d7c --- /dev/null +++ b/src/test/data/xtf-merger/ili24/delete/expected.xtf @@ -0,0 +1,16 @@ + + + + + + interlis-testbed-runner + + + + + Some text + Some more text + + + + diff --git a/src/test/data/xtf-merger/ili24/delete/patch.xtf b/src/test/data/xtf-merger/ili24/delete/patch.xtf new file mode 100644 index 0000000..d1ebc63 --- /dev/null +++ b/src/test/data/xtf-merger/ili24/delete/patch.xtf @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/data/xtf-merger/ili24/replace/data.xtf b/src/test/data/xtf-merger/ili24/replace/data.xtf new file mode 100644 index 0000000..308412e --- /dev/null +++ b/src/test/data/xtf-merger/ili24/replace/data.xtf @@ -0,0 +1,23 @@ + + + + + + interlis-testbed-runner + + + + + Some text + Some more text + + + + + + Attribute 1 + Attribute 2 + + + + diff --git a/src/test/data/xtf-merger/ili24/replace/expected.xtf b/src/test/data/xtf-merger/ili24/replace/expected.xtf new file mode 100644 index 0000000..5de0dbb --- /dev/null +++ b/src/test/data/xtf-merger/ili24/replace/expected.xtf @@ -0,0 +1,24 @@ + + + + + + interlis-testbed-runner + + + + + Some text + Some more text + + + + + + Replaced Value + + New attribute + + + + diff --git a/src/test/data/xtf-merger/ili24/replace/patch.xtf b/src/test/data/xtf-merger/ili24/replace/patch.xtf new file mode 100644 index 0000000..8595c3d --- /dev/null +++ b/src/test/data/xtf-merger/ili24/replace/patch.xtf @@ -0,0 +1,12 @@ + + + + + + Replaced Value + + New attribute + + + + diff --git a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java index 579cca3..8fae6dc 100644 --- a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java +++ b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/RunnerTest.java @@ -1,6 +1,5 @@ package ch.geowerkstatt.interlis.testbed.runner; -import org.apache.logging.log4j.Level; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,10 +53,8 @@ public void runValidatesBaseData() throws IOException { var expectedFiles = List.of(expectedBaseDataFile); assertIterableEquals(expectedFiles, validatedFiles); - var errors = appender.getMessages() - .stream() - .filter(e -> e.level().equals(Level.ERROR)); - assertEquals(0, errors.count(), "No errors should have been logged."); + var errors = appender.getErrorMessages(); + assertEquals(0, errors.size(), "No errors should have been logged."); } @Test @@ -68,11 +65,8 @@ public void runLogsValidationError() { assertFalse(runResult, "Testbed run should have failed."); - var errors = appender.getMessages() - .stream() - .filter(e -> e.level().equals(Level.ERROR)) - .toList(); + var errors = appender.getErrorMessages(); assertEquals(1, errors.size(), "One error should have been logged."); - assertEquals("Validation of base data failed.", errors.get(0).message()); + assertEquals("Validation of base data failed.", errors.getFirst()); } } diff --git a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/TestLogAppender.java b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/TestLogAppender.java index 8e0e0bd..f69bc9c 100644 --- a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/TestLogAppender.java +++ b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/TestLogAppender.java @@ -26,14 +26,13 @@ private TestLogAppender(Class loggerClass) { } public static TestLogAppender registerAppender(Class loggerClass) { - var mockedAppender = new TestLogAppender(loggerClass); + var testLogAppender = new TestLogAppender(loggerClass); + testLogAppender.start(); var logger = (Logger) LogManager.getLogger(loggerClass); - logger.addAppender(mockedAppender); - logger.setLevel(Level.ALL); + logger.get().addAppender(testLogAppender, Level.ALL, null); - mockedAppender.start(); - return mockedAppender; + return testLogAppender; } public void unregister() { @@ -45,6 +44,13 @@ public List getMessages() { return messages; } + public List getErrorMessages() { + return messages.stream() + .filter(e -> e.level().equals(Level.ERROR)) + .map(LogEntry::message) + .toList(); + } + @Override public void append(LogEvent event) { messages.add(new LogEntry(event.getLevel(), event.getMessage().getFormattedMessage())); diff --git a/src/test/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfFileMergerTest.java b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfFileMergerTest.java new file mode 100644 index 0000000..349dbd2 --- /dev/null +++ b/src/test/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfFileMergerTest.java @@ -0,0 +1,98 @@ +package ch.geowerkstatt.interlis.testbed.runner.xtf; + +import ch.geowerkstatt.interlis.testbed.runner.TestLogAppender; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.xmlunit.builder.DiffBuilder; +import org.xmlunit.diff.DefaultNodeMatcher; +import org.xmlunit.diff.ElementSelectors; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public final class XtfFileMergerTest { + private static final String DATA_BASE_PATH = "src/test/data/xtf-merger"; + + private TestLogAppender appender; + + @BeforeEach + public void setup() { + appender = TestLogAppender.registerAppender(XtfFileMerger.class); + } + + @AfterEach + public void teardown() { + appender.stop(); + appender.unregister(); + } + + @ParameterizedTest + @ValueSource(strings = {"ili23", "ili24"}) + public void addElements(String iliVersion) throws IOException { + mergeAndValidateXtf(Path.of(DATA_BASE_PATH, iliVersion, "add")); + } + + @ParameterizedTest + @ValueSource(strings = {"ili23", "ili24"}) + public void replaceElements(String iliVersion) throws IOException { + mergeAndValidateXtf(Path.of(DATA_BASE_PATH, iliVersion, "replace")); + } + + @ParameterizedTest + @ValueSource(strings = {"ili23", "ili24"}) + public void deleteElements(String iliVersion) throws IOException { + mergeAndValidateXtf(Path.of(DATA_BASE_PATH, iliVersion, "delete")); + } + + @ParameterizedTest + @ValueSource(strings = {"ili23", "ili24"}) + public void combinedOperations(String iliVersion) throws IOException { + mergeAndValidateXtf(Path.of(DATA_BASE_PATH, iliVersion, "combined")); + } + + private void mergeAndValidateXtf(Path basePath) throws IOException { + var baseFile = basePath.resolve("data.xtf"); + var patchFile = basePath.resolve("patch.xtf"); + var expectedFile = basePath.resolve("expected.xtf"); + var outputFile = basePath.resolve("output").resolve("merged.xtf"); + + var merger = new XtfFileMerger(); + + var mergeResult = merger.merge(baseFile, patchFile, outputFile); + + assertTrue(mergeResult, "Merging should have been successful."); + assertTrue(Files.exists(outputFile), "Output file should have been created."); + + assertEquals(0, appender.getErrorMessages().size(), "No errors should have been logged."); + + assertEqualXtfFiles(expectedFile, outputFile); + } + + private void assertEqualXtfFiles(Path expectedFile, Path actualFile) throws IOException { + var expectedXml = Files.readString(expectedFile); + var actualXml = Files.readString(actualFile); + + var nodeMatcher = new DefaultNodeMatcher(ElementSelectors.byName); + var diff = DiffBuilder + .compare(expectedXml) + .withTest(actualXml) + .checkForSimilar() + .withNodeMatcher(nodeMatcher) + .ignoreWhitespace() + .ignoreComments() + .build(); + + for (var difference : diff.getDifferences()) { + System.out.println(difference); + } + + assertFalse(diff.hasDifferences(), "Expected and actual XTF files should be equal."); + } +} diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000..eb9b6c4 --- /dev/null +++ b/src/test/resources/log4j2-test.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + +