From 7e784addae40261d172c69fbef0c3765eec5c30a Mon Sep 17 00:00:00 2001 From: e551763 Date: Mon, 26 Aug 2024 12:25:11 +0200 Subject: [PATCH] feat: additional Methods in ActionHook to retrieve Settings Refs: #89 --- .../interceptor_manager/model/ActionHook.java | 59 +++-- .../util/PropertiesUtils.java | 40 ++++ .../model/ActionHookTest.java | 207 ++++++++++++++++++ .../util/PropertiesUtilsTest.java | 46 ++++ 4 files changed, 338 insertions(+), 14 deletions(-) create mode 100644 src/test/java/ch/sbb/polarion/extension/interceptor_manager/model/ActionHookTest.java diff --git a/src/main/java/ch/sbb/polarion/extension/interceptor_manager/model/ActionHook.java b/src/main/java/ch/sbb/polarion/extension/interceptor_manager/model/ActionHook.java index bc28985..dc18bbe 100644 --- a/src/main/java/ch/sbb/polarion/extension/interceptor_manager/model/ActionHook.java +++ b/src/main/java/ch/sbb/polarion/extension/interceptor_manager/model/ActionHook.java @@ -5,6 +5,7 @@ import ch.sbb.polarion.extension.generic.settings.SettingId; import ch.sbb.polarion.extension.interceptor_manager.settings.HookModel; import ch.sbb.polarion.extension.interceptor_manager.util.HookManifestUtils; +import ch.sbb.polarion.extension.interceptor_manager.util.PropertiesUtils; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.polarion.alm.projects.model.IUniqueObject; @@ -20,16 +21,19 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; /** * Base class for a hook. */ @Data -@SuppressWarnings("java:S5993") //leave public constructors +@SuppressWarnings({"unused", "java:S5993"}) //leave public constructors public abstract class ActionHook { public static final String ALL_WILDCARD = "*"; @@ -89,24 +93,51 @@ public boolean isEnabled() { @JsonIgnore public abstract String getDefaultSettings(); - protected String getSettingsValue(String settingsId) { - return StringUtils.getEmptyIfNullTrimmed(loadSettings(false).getPropertiesMap().get(settingsId)); + @NotNull + protected String getSettingsValue(@NotNull String settingsId, String... selectors) { + LinkedHashMap valueMap = getSettingsValuesWithSelector(settingsId, selectors); + return StringUtils.getEmptyIfNullTrimmed(valueMap.isEmpty() ? null : valueMap.entrySet().iterator().next().getValue()); } - @SuppressWarnings("unused") - protected boolean isCommaSeparatedSettingsHasItem(String itemToCheck, String settingsName) { - List items = Arrays.asList(getSettingsValue(settingsName).split(",")); - return items.contains(itemToCheck) || items.contains(ALL_WILDCARD); + protected boolean isCommaSeparatedSettingsHasItem(String itemToCheck, @NotNull String settingsId, String... selectors) { + String itemsString = getSettingsValue(settingsId, selectors); + return ALL_WILDCARD.equals(itemsString) || Stream.of(itemsString.split(",")).map(String::trim).anyMatch(s -> Objects.equals(s, itemToCheck)); } - @SuppressWarnings("unused") - protected boolean isCommaSeparatedSettingsHasItem(String itemToCheck, String settingsName, String projectId) { - String itemsString = getSettingsValue(settingsName + DOT + projectId); - if(StringUtils.isEmpty(itemsString)) { - itemsString = getSettingsValue(settingsName + DOT + ALL_WILDCARD); + protected Boolean getSettingsValueAsBoolean(@NotNull String settingsId, String... selectors) { + String stringValue = getSettingsValue(settingsId, selectors); + return stringValue.isEmpty() ? null : Boolean.valueOf(stringValue); + } + + protected Integer getSettingsValueAsInt(@NotNull String settingsId, String... selectors) { + String stringValue = getSettingsValue(settingsId, selectors); + return stringValue.isEmpty() ? null : Integer.valueOf(stringValue); + } + + protected List getSettingsValueAsList(@NotNull String settingsId, String... selectors) { + String stringValue = getSettingsValue(settingsId, selectors); + return stringValue.isEmpty() ? new ArrayList<>() : Stream.of(stringValue.split(",")).map(String::trim).toList(); + } + + protected LinkedHashMap getSettingsValuesWithSelector(@NotNull String settingsId, String... selectors) { + return getSettingsValuesWithSelector(false, settingsId, selectors); + } + + protected LinkedHashMap getSettingsValuesWithSelector(boolean all, @NotNull String settingsId, String... selectors) { + LinkedHashMap result = new LinkedHashMap<>(); + String[] combinedSettingsIdWithSelectors = selectors == null ? new String[]{settingsId} : + Stream.concat(Stream.of(settingsId), Arrays.stream(selectors)).toArray(String[]::new); + List selectorsCombinations = PropertiesUtils.generateSelectorsCombinations(combinedSettingsIdWithSelectors); + for (String combination : selectorsCombinations) { + String value = loadSettings(false).getPropertiesMap().get(combination); + if (value != null) { + result.put(combination, value); + if (!all) { + break; + } + } } - List items = Arrays.asList(itemsString.split(",")); - return items.contains(itemToCheck) || items.contains(ALL_WILDCARD); + return result; } /** diff --git a/src/main/java/ch/sbb/polarion/extension/interceptor_manager/util/PropertiesUtils.java b/src/main/java/ch/sbb/polarion/extension/interceptor_manager/util/PropertiesUtils.java index 1b62411..826cedb 100644 --- a/src/main/java/ch/sbb/polarion/extension/interceptor_manager/util/PropertiesUtils.java +++ b/src/main/java/ch/sbb/polarion/extension/interceptor_manager/util/PropertiesUtils.java @@ -2,6 +2,9 @@ import lombok.experimental.UtilityClass; +import java.util.ArrayList; +import java.util.List; + @UtilityClass public class PropertiesUtils { @@ -47,4 +50,41 @@ public static String buildWithDescription(String... entries) { return builder.toString(); } + + /** + * Generates comma-separated strings representing all possible combinations of the provided values in same order as they come in + * the input array and a character '*', e.g. for the input values ['projectId', 'typeId', 'fieldId'] the result must be as follows: + * + * projectId.typeId.fieldId + * projectId.typeId.* + * projectId.*.fieldId= + * projectId.*.* + * *.typeId.fieldId + * *.typeId.* + * *.*.fieldId + * *.*.* + * + * + * @param selectors array of selectors + * @return all possible combinations + */ + public List generateSelectorsCombinations(String... selectors) { + if (selectors.length == 0) { + return new ArrayList<>(); + } + List combinations = new ArrayList<>(); + generateSelectorsCombinationsRecursive(selectors, 0, "", combinations); + return combinations; + } + + private void generateSelectorsCombinationsRecursive(String[] values, int index, String current, List combinations) { + if (index == values.length) { + combinations.add(current.substring(1)); // remove the leading "." + return; + } + // add the current value + generateSelectorsCombinationsRecursive(values, index + 1, current + "." + values[index], combinations); + // add the wildcard '*' + generateSelectorsCombinationsRecursive(values, index + 1, current + ".*", combinations); + } } diff --git a/src/test/java/ch/sbb/polarion/extension/interceptor_manager/model/ActionHookTest.java b/src/test/java/ch/sbb/polarion/extension/interceptor_manager/model/ActionHookTest.java new file mode 100644 index 0000000..74cfe7a --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/interceptor_manager/model/ActionHookTest.java @@ -0,0 +1,207 @@ +package ch.sbb.polarion.extension.interceptor_manager.model; + +import ch.sbb.polarion.extension.interceptor_manager.settings.HookModel; +import ch.sbb.polarion.extension.interceptor_manager.util.HookManifestUtils; +import ch.sbb.polarion.extension.interceptor_manager.util.PropertiesUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.LinkedHashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mockStatic; + +@ExtendWith(MockitoExtension.class) +class ActionHookTest { + + private MockedStatic manifestUtilsStaticMock; + + @BeforeEach + void setUp() { + manifestUtilsStaticMock = mockStatic(HookManifestUtils.class); + manifestUtilsStaticMock.when(() -> HookManifestUtils.getHookVersion(any())).thenReturn("1.0"); + } + + @AfterEach + void tearDown() { + manifestUtilsStaticMock.close(); + } + + @Test + public void testSelectors() { + + assertEquals("value", new TestHook("someProperty=value").getSettingsValue("someProperty")); + assertEquals("", new TestHook("someProperty.*=value").getSettingsValue("someProperty")); + + assertEquals("value", new TestHook("someProperty.project1.selector1=value").getSettingsValue("someProperty", "project1", "selector1")); + assertEquals("", new TestHook("someProperty.project1.selector1=value").getSettingsValue("someProperty", "project1", "selector2")); + + assertEquals("value", new TestHook("someProperty.project1.*=value").getSettingsValue("someProperty", "project1", "selector1")); + assertEquals("value", new TestHook("someProperty.project1.*=value").getSettingsValue("someProperty", "project1", "selector2")); + + assertEquals("", new TestHook("someProperty.project1.selector1=value").getSettingsValue("someProperty", "project2", "selector1")); + assertEquals("value", new TestHook("someProperty.*.selector1=value").getSettingsValue("someProperty", "project2", "selector1")); + + assertEquals("", new TestHook("someProperty.project1.selector1=value").getSettingsValue("someProperty", "project2", "selector2")); + assertEquals("value", new TestHook("someProperty.*.*=value").getSettingsValue("someProperty", "project2", "selector2")); + + assertEquals("", new TestHook("someProperty2.project1.selector1=value").getSettingsValue("someProperty", "project1", "selector1")); + assertEquals("value", new TestHook("*.project1.selector1=value").getSettingsValue("someProperty2", "project1", "selector1")); + + assertEquals("", new TestHook("someProperty1.project1.selector1=value").getSettingsValue("someProperty1", "project2", "selector1")); + assertEquals("value", new TestHook("*.*.selector1=value").getSettingsValue("someProperty1", "project2", "selector1")); + + assertEquals("value", new TestHook("*.*.*=value").getSettingsValue("someProperty1", "project2", "selector42")); + assertEquals("value", new TestHook("*.*.*=value").getSettingsValue("", "project2", "selector42")); + assertEquals("value", new TestHook("*.*.*=value").getSettingsValue("", null, null)); + assertEquals("", new TestHook("*.*.*=value").getSettingsValue("", (String) null)); + assertEquals("", new TestHook("*.*.*=value").getSettingsValue("")); + assertEquals("", new TestHook("*.*=value").getSettingsValue("", null, null)); + assertEquals("", new TestHook("*=value").getSettingsValue("", null, null)); + } + + @Test + public void testCommaSeparatedValues() { + assertTrue(new TestHook("someProperty.*=value1,value2,value3").isCommaSeparatedSettingsHasItem("value2", "someProperty", "project1")); + assertFalse(new TestHook("wrongCase.*=value1,value2,value3").isCommaSeparatedSettingsHasItem("Value2", "wrongCase", "project1")); + assertTrue(new TestHook("withSpaces.*=value1, value2, value3").isCommaSeparatedSettingsHasItem("value2", "withSpaces", "project2")); + assertFalse(new TestHook("someProperty.*=value1,value2,value3").isCommaSeparatedSettingsHasItem("value4", "someProperty", "project1")); + assertFalse(new TestHook("containsNull=value1,null,value3").isCommaSeparatedSettingsHasItem(null, "containsNull")); + assertFalse(new TestHook("noNull=value1,value2").isCommaSeparatedSettingsHasItem(null, "noNull")); + assertTrue(new TestHook("allProperty=*").isCommaSeparatedSettingsHasItem("value", "allProperty")); + assertTrue(new TestHook("allProperty=*").isCommaSeparatedSettingsHasItem(null, "allProperty")); + } + + @Test + public void testBoolean() { + assertTrue(new TestHook("someProperty=true").getSettingsValueAsBoolean("someProperty")); + assertTrue(new TestHook("someProperty=TRUE").getSettingsValueAsBoolean("someProperty")); + assertTrue(new TestHook("someProperty= True").getSettingsValueAsBoolean("someProperty")); + assertFalse(new TestHook("someProperty=false").getSettingsValueAsBoolean("someProperty")); + assertNull(new TestHook("anotherProperty=true").getSettingsValueAsBoolean("someProperty")); + assertFalse(new TestHook("someProperty=someNonBooleanString").getSettingsValueAsBoolean("someProperty")); + assertNull(new TestHook("someProperty=").getSettingsValueAsBoolean("someProperty")); + } + + @Test + public void testInt() { + assertEquals(4, new TestHook("someProperty=4").getSettingsValueAsInt("someProperty")); + assertEquals(-42, new TestHook("someProperty= -42").getSettingsValueAsInt("someProperty")); + assertThrows(NumberFormatException.class, () -> new TestHook("someProperty=1 200").getSettingsValueAsInt("someProperty")); + assertThrows(NumberFormatException.class, () -> new TestHook("someProperty=someString").getSettingsValueAsInt("someProperty")); + assertThrows(NumberFormatException.class, () -> new TestHook("someProperty=0.1").getSettingsValueAsInt("someProperty")); + } + + @Test + public void testList() { + assertEquals(List.of("1", "2", "3"), new TestHook("someProperty=1,2,3").getSettingsValueAsList("someProperty")); + // we must preserve explicit empty values, clients may filter them if they are not needed in theirs scenario + assertEquals(List.of("1", "2", "3", "", "", "", ""), new TestHook("someProperty=1, 2 ,3,,,, , ").getSettingsValueAsList("someProperty")); + assertEquals(List.of(), new TestHook("someProperty=").getSettingsValueAsList("someProperty")); + assertEquals(List.of(), new TestHook("").getSettingsValueAsList("someProperty")); + } + + @Test + public void testGetWithSelectors() { + assertEquals(linkedMap("someProperty", "4"), new TestHook("someProperty=4").getSettingsValuesWithSelector("someProperty")); + assertEquals(linkedMap("*", "4"), new TestHook("*=4").getSettingsValuesWithSelector("someProperty")); + assertEquals(linkedMap(), new TestHook("someProperty=4").getSettingsValuesWithSelector("someProperty2")); + assertEquals(linkedMap(), new TestHook("someProperty2.someSelector=4").getSettingsValuesWithSelector("someProperty2")); + + TestHook hook = new TestHook(PropertiesUtils.build( + "someValue.someProject.additionalSelector", "1", + "someValue.someProject.*", "2", + "someValue.*.additionalSelector", "3", + "someValue.*.*", "4", + "*.someProject.additionalSelector", "5", + "*.someProject.*", "6", + "*.*.additionalSelector", "7", + "*.*.*", "8" + )); + assertEquals(linkedMap("someValue.someProject.additionalSelector", "1"), hook.getSettingsValuesWithSelector("someValue", "someProject", "additionalSelector")); + assertEquals(linkedMap("someValue.someProject.*", "2"), hook.getSettingsValuesWithSelector("someValue", "someProject", "additionalSelector2")); + assertEquals(linkedMap("someValue.*.additionalSelector", "3"), hook.getSettingsValuesWithSelector("someValue", null, "additionalSelector")); + assertEquals(linkedMap("someValue.*.*", "4"), hook.getSettingsValuesWithSelector("someValue", null, "qwe")); + assertEquals(linkedMap("*.someProject.additionalSelector", "5"), hook.getSettingsValuesWithSelector("someValue2", "someProject", "additionalSelector")); + assertEquals(linkedMap("*.someProject.*", "6"), hook.getSettingsValuesWithSelector("someValue2", "someProject", "qwe")); + assertEquals(linkedMap("*.*.additionalSelector", "7"), hook.getSettingsValuesWithSelector("someValue2", "qwe", "additionalSelector")); + assertEquals(linkedMap("*.*.*", "8"), hook.getSettingsValuesWithSelector("someValue2", "qwe", "qwe2")); + assertEquals(linkedMap("*.*.*", "8"), hook.getSettingsValuesWithSelector("", null, null)); + + // property name MUST have proper comma-separated entries count: {selectors count} + 1, otherwise it is treated as a completely different + assertEquals(linkedMap(), hook.getSettingsValuesWithSelector("someValue")); + assertEquals(linkedMap(), hook.getSettingsValuesWithSelector("someValue", "someProject")); + assertEquals(linkedMap(), hook.getSettingsValuesWithSelector("someValue", "someProject", "additionalSelector", "oneMore")); + + assertEquals(linkedMap( + "someValue.someProject.additionalSelector", "1", + "someValue.someProject.*", "2", + "someValue.*.additionalSelector", "3", + "someValue.*.*", "4", + "*.someProject.additionalSelector", "5", + "*.someProject.*", "6", + "*.*.additionalSelector", "7", + "*.*.*", "8" + ), hook.getSettingsValuesWithSelector(true, "someValue", "someProject", "additionalSelector")); + + assertEquals(linkedMap( + "someValue.someProject.*", "2", + "someValue.*.*", "4", + "*.someProject.*", "6", + "*.*.*", "8" + ), hook.getSettingsValuesWithSelector(true, "someValue", "someProject", "additionalSelector2")); + + assertEquals(linkedMap( + "someValue.*.*", "4", + "*.*.*", "8" + ), hook.getSettingsValuesWithSelector(true, "someValue", "someProject2", "additionalSelector2")); + + assertEquals(linkedMap("*.*.*", "8"), hook.getSettingsValuesWithSelector(true, "someValue2", "someProject2", "additionalSelector2")); + + assertEquals(linkedMap(), hook.getSettingsValuesWithSelector(true, "someValue2", "someProject2", "additionalSelector2", "oneMore")); + } + + private LinkedHashMap linkedMap(String... values) { + LinkedHashMap map = new LinkedHashMap<>(); + for (int i = 0; i < values.length; i++) { + if (i % 2 == 0) { + map.put(values[i], values[i + 1]); + } + } + return map; + } + + private static class TestHook extends ActionHook { + + private final String settingsProperties; + + public TestHook(String settingsProperties) { + super(ItemType.WORKITEM, ActionType.SAVE, "description"); + this.settingsProperties = settingsProperties; + } + + @Override + public HookModel loadSettings(boolean forceUpdate) { + return new HookModel(true, "1.0", settingsProperties); + } + + @Override + public @NotNull HookExecutor getExecutor() { + return new HookExecutor() { + // just empty executor + }; + } + + @Override + public String getDefaultSettings() { + return ""; + } + } +} \ No newline at end of file diff --git a/src/test/java/ch/sbb/polarion/extension/interceptor_manager/util/PropertiesUtilsTest.java b/src/test/java/ch/sbb/polarion/extension/interceptor_manager/util/PropertiesUtilsTest.java index 36693de..9c051b9 100644 --- a/src/test/java/ch/sbb/polarion/extension/interceptor_manager/util/PropertiesUtilsTest.java +++ b/src/test/java/ch/sbb/polarion/extension/interceptor_manager/util/PropertiesUtilsTest.java @@ -4,6 +4,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertEquals; @ExtendWith(MockitoExtension.class) @@ -18,4 +20,48 @@ public void testBuild() { public void testBuildWithDescription() { assertEquals(PropertiesUtils.buildWithDescription("desc1", "a", "b", "desc2", "c", "d"), "# desc1" + System.lineSeparator() + "a=b" + System.lineSeparator() + "# desc2" + System.lineSeparator() + "c=d" + System.lineSeparator()); } + + @Test + public void testSelectorsCombinations() { + assertEquals(List.of(), PropertiesUtils.generateSelectorsCombinations()); + assertEquals(List.of("someValue", "*"), PropertiesUtils.generateSelectorsCombinations("someValue")); + assertEquals(List.of("null", "*"), PropertiesUtils.generateSelectorsCombinations((String) null)); + + assertEquals(List.of( + "someValue.someProject", + "someValue.*", + "*.someProject", + "*.*" + ), PropertiesUtils.generateSelectorsCombinations("someValue", "someProject")); + + assertEquals(List.of( + "someValue.someProject.additionalSelector", + "someValue.someProject.*", + "someValue.*.additionalSelector", + "someValue.*.*", + "*.someProject.additionalSelector", + "*.someProject.*", + "*.*.additionalSelector", + "*.*.*" + ), PropertiesUtils.generateSelectorsCombinations("someValue", "someProject", "additionalSelector")); + + assertEquals(List.of( + "someValue.someProject.additionalSelector.fourthSelector", + "someValue.someProject.additionalSelector.*", + "someValue.someProject.*.fourthSelector", + "someValue.someProject.*.*", + "someValue.*.additionalSelector.fourthSelector", + "someValue.*.additionalSelector.*", + "someValue.*.*.fourthSelector", + "someValue.*.*.*", + "*.someProject.additionalSelector.fourthSelector", + "*.someProject.additionalSelector.*", + "*.someProject.*.fourthSelector", + "*.someProject.*.*", + "*.*.additionalSelector.fourthSelector", + "*.*.additionalSelector.*", + "*.*.*.fourthSelector", + "*.*.*.*" + ), PropertiesUtils.generateSelectorsCombinations("someValue", "someProject", "additionalSelector", "fourthSelector")); + } }