diff --git a/agents-installer/pom.xml b/agents-installer/pom.xml
index 325d631821..66842b062d 100644
--- a/agents-installer/pom.xml
+++ b/agents-installer/pom.xml
@@ -41,5 +41,25 @@
commons-compress
${commons.compress.version}
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.jupiter.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.version}
+ test
+
diff --git a/agents-installer/src/test/java/org/apache/ranger/utils/install/TestXmlConfigChanger.java b/agents-installer/src/test/java/org/apache/ranger/utils/install/TestXmlConfigChanger.java
new file mode 100644
index 0000000000..8af1d23c51
--- /dev/null
+++ b/agents-installer/src/test/java/org/apache/ranger/utils/install/TestXmlConfigChanger.java
@@ -0,0 +1,501 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.utils.install;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for XmlConfigChanger
+ */
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestXmlConfigChanger {
+ @Test
+ void testMain_negative_missingOptionsEndsWithRuntimeException() {
+ Assertions.assertThrows(RuntimeException.class, () -> XmlConfigChanger.main(new String[0]));
+ }
+
+ @Test
+ void testMain_positive_completesWithoutExitWhenFilesValid() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-main");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "sample.key replaced mod\n");
+
+ String[] args = new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath()
+ };
+
+ Assertions.assertDoesNotThrow(() -> XmlConfigChanger.main(args));
+
+ Assertions.assertTrue(outputXml.exists());
+ Assertions.assertTrue(readUtf8(outputXml.toPath()).contains("replaced"));
+ }
+
+ @Test
+ void testParseConfig_negative_configNotReadableThrows() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-parse-cfg");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File missingCfg = tmp.resolve("nope.txt").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ String[] args = new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", missingCfg.getAbsolutePath()
+ };
+
+ Assertions.assertThrows(RuntimeException.class, () -> changer.parseConfig(args));
+ }
+
+ @Test
+ void testParseConfig_negative_installPropNotReadableThrows() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-parse-p");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+ File missingProp = tmp.resolve("missing.properties").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "a b add\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ String[] args = new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath(),
+ "-p", missingProp.getAbsolutePath()
+ };
+
+ Assertions.assertThrows(RuntimeException.class, () -> changer.parseConfig(args));
+ }
+
+ @Test
+ void testParseConfig_negative_outputAlreadyExistsThrows() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-parse-out");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "a b add\n");
+ writeUtf8(outputXml.toPath(), "exists\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ String[] args = new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath()
+ };
+
+ Assertions.assertThrows(RuntimeException.class, () -> changer.parseConfig(args));
+ }
+
+ @Test
+ void testParseConfig_negative_unreadableInputThrows() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-parse-neg");
+ tmp.toFile().deleteOnExit();
+
+ File missingIn = tmp.resolve("noin.xml").toFile();
+ File outXml = tmp.resolve("out.xml").toFile();
+ File cfg = tmp.resolve("cfg.txt").toFile();
+
+ writeUtf8(cfg.toPath(), "a b add\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ String[] args = new String[] {
+ "-i", missingIn.getAbsolutePath(),
+ "-o", outXml.getAbsolutePath(),
+ "-c", cfg.getAbsolutePath()
+ };
+
+ Assertions.assertThrows(RuntimeException.class, () -> changer.parseConfig(args));
+ }
+
+ @Test
+ void testParseConfig_positive_acceptsReadableFiles() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-parse-pos");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "sample.key v add\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ String[] args = new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath()
+ };
+
+ Assertions.assertDoesNotThrow(() -> changer.parseConfig(args));
+ }
+
+ @Test
+ void testParseConfig_positive_acceptsReadableInstallProp() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-parse-prop");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+ File installProp = tmp.resolve("install.properties").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "a b add\n");
+ writeUtf8(installProp.toPath(), "k=v\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ String[] args = new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath(),
+ "-p", installProp.getAbsolutePath()
+ };
+
+ Assertions.assertDoesNotThrow(() -> changer.parseConfig(args));
+ }
+
+ @Test
+ void testRun_negative_replacePropMissingTokenThrows() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-run-miss");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+ File installProp = tmp.resolve("install.properties").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "sample.key %NOT_HERE% mod\n");
+ writeUtf8(installProp.toPath(), "other=something\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ changer.parseConfig(new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath(),
+ "-p", installProp.getAbsolutePath()
+ });
+
+ Assertions.assertThrows(RuntimeException.class, changer::run);
+ }
+
+ @Test
+ void testRun_negative_replacePropUnclosedTokenThrows() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-run-open");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "sample.key %OPEN mod\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ changer.parseConfig(new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath()
+ });
+
+ Assertions.assertThrows(RuntimeException.class, changer::run);
+ }
+
+ @Test
+ void testRun_negative_unknownActionThrows() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-run-neg");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "sample.key x unknownverb\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ changer.parseConfig(new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath()
+ });
+
+ Assertions.assertThrows(RuntimeException.class, changer::run);
+ }
+
+ @Test
+ void testRun_positive_installPropFileIoFailureHandled() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-run-propio");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+ File propAsDir = tmp.resolve("prophasdir").toFile();
+
+ Assertions.assertTrue(propAsDir.mkdir());
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "sample.key fromenv mod\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ changer.parseConfig(new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath(),
+ "-p", propAsDir.getAbsolutePath()
+ });
+
+ Assertions.assertDoesNotThrow(changer::run);
+ Assertions.assertTrue(outputXml.exists());
+ }
+
+ @Test
+ void testRun_positive_skipsBlankCommentAndInlineCommentLines() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-run-cmnt");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(),
+ "\n"
+ + "# full line comment\n"
+ + "sample.key aftercomment mod # inline stripped\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ changer.parseConfig(new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath()
+ });
+
+ changer.run();
+ Assertions.assertTrue(readUtf8(outputXml.toPath()).contains("aftercomment"));
+ }
+
+ @Test
+ void testRun_positive_writesModifiedXml() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-run-pos");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+
+ writeUtf8(inputXml.toPath(), sampleConfigurationXml());
+ writeUtf8(configTxt.toPath(), "sample.key newvalue mod\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ changer.parseConfig(new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath()
+ });
+
+ changer.run();
+
+ Assertions.assertTrue(outputXml.exists());
+ String out = readUtf8(outputXml.toPath());
+
+ Assertions.assertTrue(out.contains("newvalue"));
+ Assertions.assertFalse(out.contains("original"));
+ }
+
+ @Test
+ void testRun_positive_exercisesAddDelAppendDelvalVarSubstAndInstallProps() throws Exception {
+ Path tmp = Files.createTempDirectory("xmlcc-run-all");
+ tmp.toFile().deleteOnExit();
+
+ File inputXml = tmp.resolve("input.xml").toFile();
+ File outputXml = tmp.resolve("output.xml").toFile();
+ File configTxt = tmp.resolve("config.txt").toFile();
+ File installProp = tmp.resolve("install.properties").toFile();
+
+ writeUtf8(inputXml.toPath(), multiPropertyConfigurationXml());
+ writeUtf8(installProp.toPath(),
+ "EMPTY_MARK=%EMPTY%\n"
+ + "PLUS=inserted\n");
+ writeUtf8(configTxt.toPath(),
+ "PREFIX pre var\n"
+ + "%PREFIX%_added val%PLUS% add\n"
+ + "inline.key plain add # tail comment\n"
+ + "to.delete x del\n"
+ + "empty.val was-empty mod\n"
+ + "empty.val %EMPTY_MARK% mod\n"
+ + "sample.key pre%PLUS%suf mod\n"
+ + "list.key second append create-if-not-exists ,\n"
+ + "brand.new appended append create-if-not-exists\n"
+ + "empty.append tail append\n"
+ + "append.dup dup append\n"
+ + "delval.list p2 delval\n"
+ + "comma.list c2 delval create-if-not-exists ,\n"
+ + "really.missing noop mod\n"
+ + "missing.key created mod create-if-not-exists\n");
+
+ XmlConfigChanger changer = new XmlConfigChanger();
+ changer.parseConfig(new String[] {
+ "-i", inputXml.getAbsolutePath(),
+ "-o", outputXml.getAbsolutePath(),
+ "-c", configTxt.getAbsolutePath(),
+ "-p", installProp.getAbsolutePath()
+ });
+
+ changer.run();
+
+ String out = readUtf8(outputXml.toPath());
+ Assertions.assertFalse(out.contains("to.delete"));
+ Assertions.assertTrue(out.contains("pre_added"));
+ Assertions.assertTrue(out.contains("inline.key"));
+ Assertions.assertTrue(out.contains("valinserted"));
+ Assertions.assertTrue(out.contains("preinsertedsuf"));
+ Assertions.assertTrue(out.contains("second"));
+ Assertions.assertTrue(out.contains("brand.new"));
+ Assertions.assertTrue(out.contains("tail"));
+ Assertions.assertTrue(out.contains("missing.key"));
+ Assertions.assertTrue(out.contains("created"));
+ Assertions.assertTrue(out.contains("p1 p3"));
+ Assertions.assertFalse(out.contains("p1 p2 p3"));
+ Assertions.assertTrue(out.contains("c1,c3"));
+ Assertions.assertFalse(out.contains("c1,c2,c3"));
+ }
+
+ @Test
+ void testValidationException_negative_nullCause() {
+ XmlConfigChanger.ValidationException ex = new XmlConfigChanger.ValidationException((Throwable) null);
+
+ Assertions.assertNull(ex.getCause());
+ }
+
+ @Test
+ void testValidationException_positive_wrapsThrowableCause() {
+ IllegalArgumentException cause = new IllegalArgumentException("nested");
+ XmlConfigChanger.ValidationException ex = new XmlConfigChanger.ValidationException(cause);
+
+ Assertions.assertSame(cause, ex.getCause());
+ }
+
+ private static String multiPropertyConfigurationXml() {
+ return "\n"
+ + "<" + XmlConfigChanger.ROOT_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">sample.key"
+ + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">orig"
+ + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">to.delete"
+ + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">x"
+ + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">list.key"
+ + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">first"
+ + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">empty.val"
+ + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">"
+ + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">empty.append"
+ + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">"
+ + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">append.dup"
+ + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">same"
+ + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">delval.list"
+ + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">p1 p2 p3"
+ + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">comma.list"
+ + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">c1,c2,c3"
+ + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + "" + XmlConfigChanger.ROOT_NODE_NAME + ">\n";
+ }
+
+ private static String sampleConfigurationXml() {
+ return "\n"
+ + "<" + XmlConfigChanger.ROOT_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.NAME_NODE_NAME + ">sample.key" + XmlConfigChanger.NAME_NODE_NAME + ">\n"
+ + " <" + XmlConfigChanger.VALUE_NODE_NAME + ">original" + XmlConfigChanger.VALUE_NODE_NAME + ">\n"
+ + " " + XmlConfigChanger.PROPERTY_NODE_NAME + ">\n"
+ + "" + XmlConfigChanger.ROOT_NODE_NAME + ">\n";
+ }
+
+ private static void writeUtf8(Path path, String text) throws IOException {
+ Files.write(path, text.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static String readUtf8(Path path) throws IOException {
+ return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+ }
+}