From e5a9a8fc3e8f7dcd891adb98b5da2d8acd0b3e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20Holozsny=C3=A1k?= Date: Thu, 6 Jul 2023 20:56:17 +0200 Subject: [PATCH] fix(core): Multiple use of nested configuration results in multiple properties (#71) (#73) - Multiple nested properties with the same base prefix was being collected under each other, now properties are only being put inside a property group if the properties start with the groups prefix, of course the unknown group is an exception --- .editorconfig | 5 +- pom.xml | 6 + .../pom.xml | 5 + .../core/generator/reader/MetadataReader.java | 61 ++-- .../generator/reader/MetadataReaderTest.java | 341 +++++++++++------- ...tadata-multiple-nested-with-same-type.json | 50 +++ ...ion-metadata-with-variable-field-name.json | 34 ++ 7 files changed, 335 insertions(+), 167 deletions(-) create mode 100644 spring-configuration-property-documenter-core/src/test/resources/regression/spring-configuration-metadata-multiple-nested-with-same-type.json create mode 100644 spring-configuration-property-documenter-core/src/test/resources/regression/spring-configuration-metadata-with-variable-field-name.json diff --git a/.editorconfig b/.editorconfig index a2bd83d..45ef719 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,5 +2,6 @@ end_of_line = lf indent_size = 4 indent_style = space -ij_java_class_count_to_use_import_on_demand = 999 -ij_java_names_count_to_use_import_on_demand = 999 +max_line_length = 140 +ij_java_class_count_to_use_import_on_demand = 500 +ij_java_names_count_to_use_import_on_demand = 500 diff --git a/pom.xml b/pom.xml index 38c7233..9a5e780 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ 3.8.1 0.12.0 3.3.0 + 2.0.0 Spring Configuration Property Documenter @@ -282,6 +283,11 @@ junit-jupiter-params ${junit-jupiter-params.version} + + org.junit-pioneer + junit-pioneer + ${junit-pioneer.version} + org.apache.maven.plugin-testing maven-plugin-testing-harness diff --git a/spring-configuration-property-documenter-core/pom.xml b/spring-configuration-property-documenter-core/pom.xml index 390027e..8f552d1 100644 --- a/spring-configuration-property-documenter-core/pom.xml +++ b/spring-configuration-property-documenter-core/pom.xml @@ -77,6 +77,11 @@ mockito-junit-jupiter test + + org.junit-pioneer + junit-pioneer + test + diff --git a/spring-configuration-property-documenter-core/src/main/java/org/rodnansol/core/generator/reader/MetadataReader.java b/spring-configuration-property-documenter-core/src/main/java/org/rodnansol/core/generator/reader/MetadataReader.java index 8c954d1..a703b48 100644 --- a/spring-configuration-property-documenter-core/src/main/java/org/rodnansol/core/generator/reader/MetadataReader.java +++ b/spring-configuration-property-documenter-core/src/main/java/org/rodnansol/core/generator/reader/MetadataReader.java @@ -78,22 +78,9 @@ public List readPropertiesAsPropertyGroupList(InputStream metadat } } - private PropertyGroup setProperties(Map> propertyMap, PropertyGroup propertyGroup) { - List properties = propertyMap.get(propertyGroup.getType()); - if (properties == null || properties.isEmpty()) { - LOGGER.warn("Property group with name:[{}] is having no properties, please check if you provided the getter/setter methods. If your class is empty intentionally, please forget this warning here.", propertyGroup.getGroupName()); - return propertyGroup; - } - List collectedProperties = properties.stream() - .map(property -> updateProperty(propertyGroup, property)) - .collect(Collectors.toList()); - propertyGroup.setProperties(collectedProperties); - return propertyGroup; - } - private Property updateProperty(PropertyGroup propertyGroup, Property property) { String groupName = propertyGroup.getGroupName(); - if(propertyGroup.isUnknownGroup()) { + if (propertyGroup.isUnknownGroup()) { property.setKey(property.getFqName()); } else { property.setKey(property.getFqName().substring(groupName.length() + 1)); @@ -103,9 +90,9 @@ private Property updateProperty(PropertyGroup propertyGroup, Property property) private List flattenValues(Map> propertyGroupsByType) { return propertyGroupsByType.values() - .stream() - .flatMap(Collection::stream) - .collect(Collectors.toList()); + .stream() + .flatMap(Collection::stream) + .collect(Collectors.toList()); } private void updateGroupsWithPropertiesAndAssociations(Map> propertyMap, Map> propertyGroupsByType) { @@ -123,18 +110,32 @@ private void updateGroupsWithPropertiesAndAssociations(Map updatePropertiesAndReturnNestedGroups(Map> propertyMap, Map.Entry> propertyEntry) { return propertyEntry.getValue() - .stream() - .map(propertyGroup -> setProperties(propertyMap, propertyGroup)) - .filter(PropertyGroup::isNested) - .collect(Collectors.toList()); + .stream() + .map(propertyGroup -> setProperties(propertyMap, propertyGroup)) + .filter(PropertyGroup::isNested) + .collect(Collectors.toList()); + } + + private PropertyGroup setProperties(Map> propertyMap, PropertyGroup propertyGroup) { + List properties = propertyMap.get(propertyGroup.getType()); + if (properties == null || properties.isEmpty()) { + LOGGER.warn("Property group with name:[{}] is having no properties, please check if you provided the getter/setter methods. If your class is empty intentionally, please forget this warning here.", propertyGroup.getGroupName()); + return propertyGroup; + } + List collectedProperties = properties.stream() + .filter(property -> property.getFqName().startsWith(propertyGroup.getGroupName()) || propertyGroup.isUnknownGroup()) + .map(property -> updateProperty(propertyGroup, property)) + .collect(Collectors.toList()); + propertyGroup.setProperties(collectedProperties); + return propertyGroup; } private Map> getPropertyGroups(ConfigurationMetadata configurationMetadata) { Map> propertyGroupMap = configurationMetadata.getItems() - .stream() - .filter(itemMetadata -> itemMetadata.isOfItemType(ItemMetadata.ItemType.GROUP)) - .map(itemMetadata -> new PropertyGroup(itemMetadata.getName(), itemMetadata.getType(), getSourceTypeOrDefault(itemMetadata))) - .collect(Collectors.groupingBy(PropertyGroup::getSourceType, Collectors.toList())); + .stream() + .filter(itemMetadata -> itemMetadata.isOfItemType(ItemMetadata.ItemType.GROUP)) + .map(itemMetadata -> new PropertyGroup(itemMetadata.getName(), itemMetadata.getType(), getSourceTypeOrDefault(itemMetadata))) + .collect(Collectors.groupingBy(PropertyGroup::getSourceType, Collectors.toList())); List value = new ArrayList<>(); value.add(PropertyGroup.createUnknownGroup()); propertyGroupMap.put(PropertyGroupConstants.UNKNOWN, value); @@ -144,11 +145,11 @@ private Map> getPropertyGroups(ConfigurationMetadata private Map> getPropertyMap(ConfigurationMetadata configurationMetadata) { Function getSourceType = this::getSourceTypeOrDefault; return configurationMetadata.getItems() - .stream() - .filter(itemMetadata -> itemMetadata.isOfItemType(ItemMetadata.ItemType.PROPERTY)) - .collect(Collectors.groupingBy(getSourceType, - Collectors.mapping(this::mapToProperty, Collectors.toList())) - ); + .stream() + .filter(itemMetadata -> itemMetadata.isOfItemType(ItemMetadata.ItemType.PROPERTY)) + .collect(Collectors.groupingBy(getSourceType, + Collectors.mapping(this::mapToProperty, Collectors.toList())) + ); } private String getSourceTypeOrDefault(ItemMetadata current) { diff --git a/spring-configuration-property-documenter-core/src/test/java/org/rodnansol/core/generator/reader/MetadataReaderTest.java b/spring-configuration-property-documenter-core/src/test/java/org/rodnansol/core/generator/reader/MetadataReaderTest.java index 80c301e..48dd226 100644 --- a/spring-configuration-property-documenter-core/src/test/java/org/rodnansol/core/generator/reader/MetadataReaderTest.java +++ b/spring-configuration-property-documenter-core/src/test/java/org/rodnansol/core/generator/reader/MetadataReaderTest.java @@ -1,10 +1,13 @@ package org.rodnansol.core.generator.reader; import org.assertj.core.api.AssertionsForClassTypes; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.Issue; import org.rodnansol.core.generator.template.data.Property; import org.rodnansol.core.generator.template.data.PropertyDeprecation; import org.rodnansol.core.generator.template.data.PropertyGroup; @@ -29,138 +32,138 @@ class MetadataReaderTest { static Stream validMetadataFileTestCasesWithPropertyGroups() { return Stream.of( - arguments(named("Should read file and return empty property groups list when the groups and properties are empty", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-groups-and-properties.json", - List::of))), - arguments(named("Should read file and return non-empty property groups list when the groups are empty but the properties are not and properties are not having associated groups, in this case a new 'Unknown group' group should appear", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-sourceType.json", - () -> { - PropertyGroup expectedMyProperties = new PropertyGroup("Unknown group", "Unknown", "Unknown"); - expectedMyProperties.setUnknownGroup(true); - expectedMyProperties.addProperty(new Property("myproduct.features.foobar.enabled", "java.lang.Boolean", "myproduct.features.foobar.enabled", "Enable the foobar feature", "true", null)); - return List.of(expectedMyProperties); - }))), - arguments(named("Should read file and return empty property groups list when the groups and properties are empty", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-groups-and-properties.json", - List::of))), - arguments(named("Should read file and return non-empty property groups list when one group is given but no associated properties are present", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-one-group-empty-properties.json", - () -> { - PropertyGroup expectedMyProperties = new PropertyGroup("this.is.my", "com.example.springpropertysources.MyProperties", "com.example.springpropertysources.MyProperties"); - return List.of(expectedMyProperties); - }))), - arguments(named("Should read file and return non-empty property groups list when one group is present with one property", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-one-group-one-property.json", - () -> { - PropertyGroup expectedMyProperties = new PropertyGroup("this.is.my", "com.example.springpropertysources.MyProperties", "com.example.springpropertysources.MyProperties"); - expectedMyProperties.addProperty(new Property("this.is.my.variable", "java.lang.String", "variable", "This is my variable.", null, null)); - return List.of(expectedMyProperties); - }))), - arguments(named("Should read file and return non-empty property groups list when the groups are having nested associations", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-with-nested-property.json", - () -> { - PropertyGroup expectedMyProperties = new PropertyGroup("this.is.my", "com.example.springpropertysources.MyProperties", "com.example.springpropertysources.MyProperties"); - expectedMyProperties.addProperty(new Property("this.is.my.variable", "java.lang.String", "variable", "This is my variable.", null, null)); - - PropertyGroup expectedFirstLevelNestedProperty = new PropertyGroup("this.is.my.first-level-nested-property", "com.example.springpropertysources.FirstLevelNestedProperty", "com.example.springpropertysources.MyProperties"); - expectedFirstLevelNestedProperty.setNested(true); - expectedFirstLevelNestedProperty.addProperty(new Property("this.is.my.first-level-nested-property.desc", "java.lang.String", "desc", "Description of this thing.", "123", null)); - expectedFirstLevelNestedProperty.addProperty(new Property("this.is.my.first-level-nested-property.name", "java.lang.String", "name", "Name of the custom property.", "ABC", null)); - expectedFirstLevelNestedProperty.setParentGroup(expectedMyProperties); - - PropertyGroup firstLevelNestedPropertySecondLevelNestedClass = new PropertyGroup("this.is.my.first-level-nested-property.second-level-nested-class", "com.example.springpropertysources.FirstLevelNestedProperty$SecondLevelNestedClass", "com.example.springpropertysources.FirstLevelNestedProperty"); - firstLevelNestedPropertySecondLevelNestedClass.setNested(true); - firstLevelNestedPropertySecondLevelNestedClass.addProperty(new Property("this.is.my.first-level-nested-property.second-level-nested-class.second-level-value", "java.lang.String", "second-level-value", "Custom nested", null, null)); - - PropertyGroup expectedNestedProperties = new PropertyGroup("this.is.my.nested", "com.example.springpropertysources.TopLevelClassNestedProperty", "com.example.springpropertysources.TopLevelClassNestedProperty"); - expectedNestedProperties.addProperty(new Property("this.is.my.nested.nested-value", "java.lang.String", "nested-value", "Nested value.", null, null)); - - return List.of(expectedMyProperties, expectedFirstLevelNestedProperty, PropertyGroup.createUnknownGroup(), firstLevelNestedPropertySecondLevelNestedClass, expectedNestedProperties); - }))), - arguments(named("Should read file and return non-empty property groups list when the groups and properties are in complex relation", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-complex.json", - () -> { - PropertyGroup expectedYourProperties = new PropertyGroup("this.is.your", "com.example.springpropertysources.YourProperties", "com.example.springpropertysources.YourProperties"); - expectedYourProperties.addProperty(new Property("this.is.your.property", "java.lang.String", "property", "This is YOUR property.", null, null)); - - PropertyGroup expectedMyProperties = new PropertyGroup("this.is.my", "com.example.springpropertysources.MyProperties", "com.example.springpropertysources.MyProperties"); - expectedMyProperties.addProperty(new Property("this.is.my.another-variable", "java.lang.String", "another-variable", null, "with default value", new PropertyDeprecation(null, null))); - expectedMyProperties.addProperty(new Property("this.is.my.date", "java.time.LocalDate", "date", null, null, null)); - expectedMyProperties.addProperty(new Property("this.is.my.date-time", "java.time.LocalDateTime", "date-time", null, null, null)); - expectedMyProperties.addProperty(new Property("this.is.my.duration", "java.time.Duration", "duration", "A duration.", "2d", new PropertyDeprecation("Because it is deprecated", "instant"))); - expectedMyProperties.addProperty(new Property("this.is.my.instant", "java.time.Instant", "instant", null, "123", null)); - expectedMyProperties.addProperty(new Property("this.is.my.variable", "java.lang.String", "variable", "This is my variable.", null, null)); - - PropertyGroup expectedFirstLevelNestedProperty = new PropertyGroup("this.is.my.first-level-nested-property", "com.example.springpropertysources.FirstLevelNestedProperty", "com.example.springpropertysources.MyProperties"); - expectedFirstLevelNestedProperty.setNested(true); - expectedFirstLevelNestedProperty.addProperty(new Property("this.is.my.first-level-nested-property.desc", "java.lang.String", "desc", "Description of this thing.", "123", null)); - expectedFirstLevelNestedProperty.addProperty(new Property("this.is.my.first-level-nested-property.name", "java.lang.String", "name", "Name of the custom property.", "ABC", null)); - expectedFirstLevelNestedProperty.setParentGroup(expectedMyProperties); - - PropertyGroup firstLevelNestedPropertySecondLevelNestedClass = new PropertyGroup("this.is.my.first-level-nested-property.second-level-nested-class", "com.example.springpropertysources.FirstLevelNestedProperty$SecondLevelNestedClass", "com.example.springpropertysources.FirstLevelNestedProperty"); - firstLevelNestedPropertySecondLevelNestedClass.setNested(true); - firstLevelNestedPropertySecondLevelNestedClass.addProperty(new Property("this.is.my.first-level-nested-property.second-level-nested-class.second-level-value", "java.lang.String", "second-level-value", "Custom nested", null, null)); - - PropertyGroup expectedNestedProperties = new PropertyGroup("this.is.my.nested", "com.example.springpropertysources.TopLevelClassNestedProperty", "com.example.springpropertysources.TopLevelClassNestedProperty"); - expectedNestedProperties.addProperty(new Property("this.is.my.nested.nested-value", "java.lang.String", "nested-value", "Nested value.", null, null)); - - return List.of(expectedYourProperties, expectedMyProperties, expectedFirstLevelNestedProperty, PropertyGroup.createUnknownGroup(), firstLevelNestedPropertySecondLevelNestedClass, expectedNestedProperties); - }))) + arguments(named("Should read file and return empty property groups list when the groups and properties are empty", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-groups-and-properties.json", + List::of))), + arguments(named("Should read file and return non-empty property groups list when the groups are empty but the properties are not and properties are not having associated groups, in this case a new 'Unknown group' group should appear", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-sourceType.json", + () -> { + PropertyGroup expectedMyProperties = new PropertyGroup("Unknown group", "Unknown", "Unknown"); + expectedMyProperties.setUnknownGroup(true); + expectedMyProperties.addProperty(new Property("myproduct.features.foobar.enabled", "java.lang.Boolean", "myproduct.features.foobar.enabled", "Enable the foobar feature", "true", null)); + return List.of(expectedMyProperties); + }))), + arguments(named("Should read file and return empty property groups list when the groups and properties are empty", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-groups-and-properties.json", + List::of))), + arguments(named("Should read file and return non-empty property groups list when one group is given but no associated properties are present", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-one-group-empty-properties.json", + () -> { + PropertyGroup expectedMyProperties = new PropertyGroup("this.is.my", "com.example.springpropertysources.MyProperties", "com.example.springpropertysources.MyProperties"); + return List.of(expectedMyProperties); + }))), + arguments(named("Should read file and return non-empty property groups list when one group is present with one property", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-one-group-one-property.json", + () -> { + PropertyGroup expectedMyProperties = new PropertyGroup("this.is.my", "com.example.springpropertysources.MyProperties", "com.example.springpropertysources.MyProperties"); + expectedMyProperties.addProperty(new Property("this.is.my.variable", "java.lang.String", "variable", "This is my variable.", null, null)); + return List.of(expectedMyProperties); + }))), + arguments(named("Should read file and return non-empty property groups list when the groups are having nested associations", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-with-nested-property.json", + () -> { + PropertyGroup expectedMyProperties = new PropertyGroup("this.is.my", "com.example.springpropertysources.MyProperties", "com.example.springpropertysources.MyProperties"); + expectedMyProperties.addProperty(new Property("this.is.my.variable", "java.lang.String", "variable", "This is my variable.", null, null)); + + PropertyGroup expectedFirstLevelNestedProperty = new PropertyGroup("this.is.my.first-level-nested-property", "com.example.springpropertysources.FirstLevelNestedProperty", "com.example.springpropertysources.MyProperties"); + expectedFirstLevelNestedProperty.setNested(true); + expectedFirstLevelNestedProperty.addProperty(new Property("this.is.my.first-level-nested-property.desc", "java.lang.String", "desc", "Description of this thing.", "123", null)); + expectedFirstLevelNestedProperty.addProperty(new Property("this.is.my.first-level-nested-property.name", "java.lang.String", "name", "Name of the custom property.", "ABC", null)); + expectedFirstLevelNestedProperty.setParentGroup(expectedMyProperties); + + PropertyGroup firstLevelNestedPropertySecondLevelNestedClass = new PropertyGroup("this.is.my.first-level-nested-property.second-level-nested-class", "com.example.springpropertysources.FirstLevelNestedProperty$SecondLevelNestedClass", "com.example.springpropertysources.FirstLevelNestedProperty"); + firstLevelNestedPropertySecondLevelNestedClass.setNested(true); + firstLevelNestedPropertySecondLevelNestedClass.addProperty(new Property("this.is.my.first-level-nested-property.second-level-nested-class.second-level-value", "java.lang.String", "second-level-value", "Custom nested", null, null)); + + PropertyGroup expectedNestedProperties = new PropertyGroup("this.is.my.nested", "com.example.springpropertysources.TopLevelClassNestedProperty", "com.example.springpropertysources.TopLevelClassNestedProperty"); + expectedNestedProperties.addProperty(new Property("this.is.my.nested.nested-value", "java.lang.String", "nested-value", "Nested value.", null, null)); + + return List.of(expectedMyProperties, expectedFirstLevelNestedProperty, PropertyGroup.createUnknownGroup(), firstLevelNestedPropertySecondLevelNestedClass, expectedNestedProperties); + }))), + arguments(named("Should read file and return non-empty property groups list when the groups and properties are in complex relation", new ReadPropertiesAsPropertyGroupListTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-complex.json", + () -> { + PropertyGroup expectedYourProperties = new PropertyGroup("this.is.your", "com.example.springpropertysources.YourProperties", "com.example.springpropertysources.YourProperties"); + expectedYourProperties.addProperty(new Property("this.is.your.property", "java.lang.String", "property", "This is YOUR property.", null, null)); + + PropertyGroup expectedMyProperties = new PropertyGroup("this.is.my", "com.example.springpropertysources.MyProperties", "com.example.springpropertysources.MyProperties"); + expectedMyProperties.addProperty(new Property("this.is.my.another-variable", "java.lang.String", "another-variable", null, "with default value", new PropertyDeprecation(null, null))); + expectedMyProperties.addProperty(new Property("this.is.my.date", "java.time.LocalDate", "date", null, null, null)); + expectedMyProperties.addProperty(new Property("this.is.my.date-time", "java.time.LocalDateTime", "date-time", null, null, null)); + expectedMyProperties.addProperty(new Property("this.is.my.duration", "java.time.Duration", "duration", "A duration.", "2d", new PropertyDeprecation("Because it is deprecated", "instant"))); + expectedMyProperties.addProperty(new Property("this.is.my.instant", "java.time.Instant", "instant", null, "123", null)); + expectedMyProperties.addProperty(new Property("this.is.my.variable", "java.lang.String", "variable", "This is my variable.", null, null)); + + PropertyGroup expectedFirstLevelNestedProperty = new PropertyGroup("this.is.my.first-level-nested-property", "com.example.springpropertysources.FirstLevelNestedProperty", "com.example.springpropertysources.MyProperties"); + expectedFirstLevelNestedProperty.setNested(true); + expectedFirstLevelNestedProperty.addProperty(new Property("this.is.my.first-level-nested-property.desc", "java.lang.String", "desc", "Description of this thing.", "123", null)); + expectedFirstLevelNestedProperty.addProperty(new Property("this.is.my.first-level-nested-property.name", "java.lang.String", "name", "Name of the custom property.", "ABC", null)); + expectedFirstLevelNestedProperty.setParentGroup(expectedMyProperties); + + PropertyGroup firstLevelNestedPropertySecondLevelNestedClass = new PropertyGroup("this.is.my.first-level-nested-property.second-level-nested-class", "com.example.springpropertysources.FirstLevelNestedProperty$SecondLevelNestedClass", "com.example.springpropertysources.FirstLevelNestedProperty"); + firstLevelNestedPropertySecondLevelNestedClass.setNested(true); + firstLevelNestedPropertySecondLevelNestedClass.addProperty(new Property("this.is.my.first-level-nested-property.second-level-nested-class.second-level-value", "java.lang.String", "second-level-value", "Custom nested", null, null)); + + PropertyGroup expectedNestedProperties = new PropertyGroup("this.is.my.nested", "com.example.springpropertysources.TopLevelClassNestedProperty", "com.example.springpropertysources.TopLevelClassNestedProperty"); + expectedNestedProperties.addProperty(new Property("this.is.my.nested.nested-value", "java.lang.String", "nested-value", "Nested value.", null, null)); + + return List.of(expectedYourProperties, expectedMyProperties, expectedFirstLevelNestedProperty, PropertyGroup.createUnknownGroup(), firstLevelNestedPropertySecondLevelNestedClass, expectedNestedProperties); + }))) ); } static Stream validMetadataFileTestCasesWithMaps() { return Stream.of( - arguments(named("Should read file and return empty map when the groups and properties are empty", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-groups-and-properties.json", - Map::of))), - arguments(named("Should read file and return empty map when the groups and properties are empty", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-groups-and-properties.json", - Map::of))), - arguments(named("Should read file and return empty map when one group is given but no associated properties are present", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-one-group-empty-properties.json", - Map::of))), - arguments(named("Should read file and return non-empty map when one group is present with one property", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-one-group-one-property.json", - () -> { - return Map.ofEntries( - entry("com.example.springpropertysources.MyProperties", List.of( - new Property("this.is.my.variable", "java.lang.String", null, "This is my variable.", null, null) - )) - ); - }))), - arguments(named("Should read file and return non-empty map when the groups are having nested associations", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-with-nested-property.json", - () -> { - return Map.ofEntries( - entry("com.example.springpropertysources.MyProperties", List.of( - new Property("this.is.my.variable", "java.lang.String", null, "This is my variable.", null, null) - )), - entry("com.example.springpropertysources.FirstLevelNestedProperty", List.of( - new Property("this.is.my.first-level-nested-property.desc", "java.lang.String", null, "Description of this thing.", "123", null), - new Property("this.is.my.first-level-nested-property.name", "java.lang.String", null, "Name of the custom property.", "ABC", null) - )), - entry("com.example.springpropertysources.FirstLevelNestedProperty$SecondLevelNestedClass", List.of( - new Property("this.is.my.first-level-nested-property.second-level-nested-class.second-level-value", "java.lang.String", null, "Custom nested", null, null) - )), - entry("com.example.springpropertysources.TopLevelClassNestedProperty", List.of( - new Property("this.is.my.nested.nested-value", "java.lang.String", null, "Nested value.", null, null) - )) - ); - - }))), - arguments(named("Should read file and return non-empty map when the groups and properties are in complex relation", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-complex.json", - () -> { - return Map.ofEntries( - entry("com.example.springpropertysources.YourProperties", List.of(new Property("this.is.your.property", "java.lang.String", null, "This is YOUR property.", null, null))), - entry("com.example.springpropertysources.MyProperties", List.of( - new Property("this.is.my.another-variable", "java.lang.String", null, null, "with default value", new PropertyDeprecation(null, null)), - new Property("this.is.my.date", "java.time.LocalDate", null, null, null, null), - new Property("this.is.my.date-time", "java.time.LocalDateTime", null, null, null, null), - new Property("this.is.my.duration", "java.time.Duration", null, "A duration.", "2d", new PropertyDeprecation("Because it is deprecated", "instant")), - new Property("this.is.my.instant", "java.time.Instant", null, null, "123", null), - new Property("this.is.my.variable", "java.lang.String", null, "This is my variable.", null, null) - )), - entry("com.example.springpropertysources.FirstLevelNestedProperty", List.of( - new Property("this.is.my.first-level-nested-property.desc", "java.lang.String", null, "Description of this thing.", "123", null), - new Property("this.is.my.first-level-nested-property.name", "java.lang.String", null, "Name of the custom property.", "ABC", null) - )), - entry("com.example.springpropertysources.FirstLevelNestedProperty$SecondLevelNestedClass", List.of( - new Property("this.is.my.first-level-nested-property.second-level-nested-class.second-level-value", "java.lang.String", null, "Custom nested", null, null) - )), - entry("com.example.springpropertysources.TopLevelClassNestedProperty", List.of( - new Property("this.is.my.nested.nested-value", "java.lang.String", null, "Nested value.", null, null) - )) - ); - }))) + arguments(named("Should read file and return empty map when the groups and properties are empty", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-groups-and-properties.json", + Map::of))), + arguments(named("Should read file and return empty map when the groups and properties are empty", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty-groups-and-properties.json", + Map::of))), + arguments(named("Should read file and return empty map when one group is given but no associated properties are present", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-one-group-empty-properties.json", + Map::of))), + arguments(named("Should read file and return non-empty map when one group is present with one property", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-one-group-one-property.json", + () -> { + return Map.ofEntries( + entry("com.example.springpropertysources.MyProperties", List.of( + new Property("this.is.my.variable", "java.lang.String", null, "This is my variable.", null, null) + )) + ); + }))), + arguments(named("Should read file and return non-empty map when the groups are having nested associations", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-with-nested-property.json", + () -> { + return Map.ofEntries( + entry("com.example.springpropertysources.MyProperties", List.of( + new Property("this.is.my.variable", "java.lang.String", null, "This is my variable.", null, null) + )), + entry("com.example.springpropertysources.FirstLevelNestedProperty", List.of( + new Property("this.is.my.first-level-nested-property.desc", "java.lang.String", null, "Description of this thing.", "123", null), + new Property("this.is.my.first-level-nested-property.name", "java.lang.String", null, "Name of the custom property.", "ABC", null) + )), + entry("com.example.springpropertysources.FirstLevelNestedProperty$SecondLevelNestedClass", List.of( + new Property("this.is.my.first-level-nested-property.second-level-nested-class.second-level-value", "java.lang.String", null, "Custom nested", null, null) + )), + entry("com.example.springpropertysources.TopLevelClassNestedProperty", List.of( + new Property("this.is.my.nested.nested-value", "java.lang.String", null, "Nested value.", null, null) + )) + ); + + }))), + arguments(named("Should read file and return non-empty map when the groups and properties are in complex relation", new ReadPropertiesAsMapTestCase(TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-complex.json", + () -> { + return Map.ofEntries( + entry("com.example.springpropertysources.YourProperties", List.of(new Property("this.is.your.property", "java.lang.String", null, "This is YOUR property.", null, null))), + entry("com.example.springpropertysources.MyProperties", List.of( + new Property("this.is.my.another-variable", "java.lang.String", null, null, "with default value", new PropertyDeprecation(null, null)), + new Property("this.is.my.date", "java.time.LocalDate", null, null, null, null), + new Property("this.is.my.date-time", "java.time.LocalDateTime", null, null, null, null), + new Property("this.is.my.duration", "java.time.Duration", null, "A duration.", "2d", new PropertyDeprecation("Because it is deprecated", "instant")), + new Property("this.is.my.instant", "java.time.Instant", null, null, "123", null), + new Property("this.is.my.variable", "java.lang.String", null, "This is my variable.", null, null) + )), + entry("com.example.springpropertysources.FirstLevelNestedProperty", List.of( + new Property("this.is.my.first-level-nested-property.desc", "java.lang.String", null, "Description of this thing.", "123", null), + new Property("this.is.my.first-level-nested-property.name", "java.lang.String", null, "Name of the custom property.", "ABC", null) + )), + entry("com.example.springpropertysources.FirstLevelNestedProperty$SecondLevelNestedClass", List.of( + new Property("this.is.my.first-level-nested-property.second-level-nested-class.second-level-value", "java.lang.String", null, "Custom nested", null, null) + )), + entry("com.example.springpropertysources.TopLevelClassNestedProperty", List.of( + new Property("this.is.my.nested.nested-value", "java.lang.String", null, "Nested value.", null, null) + )) + ); + }))) ); } @@ -174,19 +177,19 @@ void readPropertiesAsPropertyGroupList_shouldReturnListOfPropertyGroup_whenInput // Then assertThat(propertyGroups) - .containsAll(readPropertiesAsPropertyGroupListTestCase.expectedPropertyGroupsSupplier.get()); + .containsAll(readPropertiesAsPropertyGroupListTestCase.expectedPropertyGroupsSupplier.get()); } @ParameterizedTest @ValueSource(strings = { - TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty.json", - TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-invalid.json"}) + TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty.json", + TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-invalid.json"}) void readPropertiesAsPropertyGroupList_shouldThrowMetadataConversionException_whenAnErrorOccursDuringConversion(String metadataFileLocation) throws FileNotFoundException { // Given // When AssertionsForClassTypes.assertThatThrownBy(() -> underTest.readPropertiesAsPropertyGroupList(new FileInputStream(metadataFileLocation))) - .isInstanceOf(MetadataConversionException.class); + .isInstanceOf(MetadataConversionException.class); // Then } @@ -201,23 +204,91 @@ void readPropertiesAsMap_shouldReturnListOfPropertyGroup_whenInputFileContainsPr // Then assertThat(resultMap) - .containsAllEntriesOf(testCase.expectedPropertyMapSupplier.get()); + .containsAllEntriesOf(testCase.expectedPropertyMapSupplier.get()); } @ParameterizedTest @ValueSource(strings = { - TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty.json", - TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-invalid.json"}) + TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-empty.json", + TEST_RESOURCES_DIRECTORY + "spring-configuration-metadata-invalid.json"}) void readPropertiesAsMap_shouldThrowMetadataConversionException_whenAnErrorOccursDuringConversion(String metadataFileLocation) throws FileNotFoundException { // Given // When AssertionsForClassTypes.assertThatThrownBy(() -> underTest.readPropertiesAsMap(new FileInputStream(metadataFileLocation))) - .isInstanceOf(MetadataConversionException.class); + .isInstanceOf(MetadataConversionException.class); // Then } + /** + * RegressionTests class. + */ + @Nested + class RegressionTests { + + @Test + @Issue("#71") + void readPropertiesAsPropertyGroupList_whenSingleTypeIsBeingReusedMultipleTimes() throws FileNotFoundException { + // Given + String fileName = TEST_RESOURCES_DIRECTORY + "regression/spring-configuration-metadata-multiple-nested-with-same-type.json"; + + // When + List propertyGroups = underTest.readPropertiesAsPropertyGroupList(new FileInputStream(fileName)); + + // Then + PropertyGroup topLevelGroup = new PropertyGroup("my.app.parent.my-app-configuration", "com.playground.springplayground.MyApplicationConfiguration", "com.playground.springplayground.MyApplicationConfiguration"); + topLevelGroup.addProperty(new Property("my.app.parent.my-app-configuration.foo", "java.lang.String", "foo", null, null, null)); + + PropertyGroup nestedFirst = new PropertyGroup("my.app.parent.my-app-configuration.abc", "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", "com.playground.springplayground.MyApplicationConfiguration"); + nestedFirst.setNested(true); + nestedFirst.setParentGroup(topLevelGroup); + nestedFirst.addProperty(new Property("my.app.parent.my-app-configuration.abc.bar", "java.lang.String", "bar", null, null, null)); + + PropertyGroup nestedSecond = new PropertyGroup("my.app.parent.my-app-configuration.efg", "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", "com.playground.springplayground.MyApplicationConfiguration"); + nestedSecond.setNested(true); + nestedSecond.setParentGroup(topLevelGroup); + nestedSecond.addProperty(new Property("my.app.parent.my-app-configuration.efg.bar", "java.lang.String", "bar", null, null, null)); + + PropertyGroup nestedThird = new PropertyGroup("my.app.parent.my-app-configuration.xyz", "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", "com.playground.springplayground.MyApplicationConfiguration"); + nestedThird.setNested(true); + nestedThird.setParentGroup(topLevelGroup); + nestedThird.addProperty(new Property("my.app.parent.my-app-configuration.xyz.bar", "java.lang.String", "bar", null, null, null)); + + + List expectedYourProperties1 = List.of(PropertyGroup.createUnknownGroup(), topLevelGroup, nestedFirst, nestedSecond, nestedThird); + assertThat(propertyGroups) + .containsAll(expectedYourProperties1); + } + + @Test + @Issue("#72") + void readPropertiesAsPropertyGroupList() throws FileNotFoundException { + // Given + String fileName = TEST_RESOURCES_DIRECTORY + "regression/spring-configuration-metadata-with-variable-field-name.json"; + + // When + List propertyGroups = underTest.readPropertiesAsPropertyGroupList(new FileInputStream(fileName)); + + // Then + PropertyGroup topLevelGroup = new PropertyGroup("my.app.parent.my-app-configuration", "com.playground.springplayground.MyApplicationConfiguration", "com.playground.springplayground.MyApplicationConfiguration"); + + PropertyGroup nestedFirst = new PropertyGroup("my.app.parent.my-app-configuration.abcd", "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", "com.playground.springplayground.MyApplicationConfiguration"); + nestedFirst.setNested(true); + nestedFirst.setParentGroup(topLevelGroup); + nestedFirst.addProperty(new Property("my.app.parent.my-app-configuration.abcd.ab", "java.lang.String", "ab", null, null, null)); + + PropertyGroup nestedSecond = new PropertyGroup("my.app.parent.my-app-configuration.another-child-config", "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", "com.playground.springplayground.MyApplicationConfiguration"); + nestedSecond.setNested(true); + nestedSecond.setParentGroup(topLevelGroup); + nestedSecond.addProperty(new Property("my.app.parent.my-app-configuration.another-child-config.ab", "java.lang.String", "ab", null, null, null)); + + List expectedYourProperties1 = List.of(PropertyGroup.createUnknownGroup(), topLevelGroup, nestedFirst, nestedSecond); + assertThat(propertyGroups) + .containsAll(expectedYourProperties1); + } + } + static class ReadPropertiesAsPropertyGroupListTestCase { public final String metadataFileLocation; diff --git a/spring-configuration-property-documenter-core/src/test/resources/regression/spring-configuration-metadata-multiple-nested-with-same-type.json b/spring-configuration-property-documenter-core/src/test/resources/regression/spring-configuration-metadata-multiple-nested-with-same-type.json new file mode 100644 index 0000000..8918e4d --- /dev/null +++ b/spring-configuration-property-documenter-core/src/test/resources/regression/spring-configuration-metadata-multiple-nested-with-same-type.json @@ -0,0 +1,50 @@ +{ + "groups": [ + { + "name": "my.app.parent.my-app-configuration", + "type": "com.playground.springplayground.MyApplicationConfiguration", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration" + }, + { + "name": "my.app.parent.my-app-configuration.abc", + "type": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration", + "sourceMethod": "abc()" + }, + { + "name": "my.app.parent.my-app-configuration.efg", + "type": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration", + "sourceMethod": "efg()" + }, + { + "name": "my.app.parent.my-app-configuration.xyz", + "type": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration", + "sourceMethod": "xyz()" + } + ], + "properties": [ + { + "name": "my.app.parent.my-app-configuration.abc.bar", + "type": "java.lang.String", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration" + }, + { + "name": "my.app.parent.my-app-configuration.efg.bar", + "type": "java.lang.String", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration" + }, + { + "name": "my.app.parent.my-app-configuration.foo", + "type": "java.lang.String", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration" + }, + { + "name": "my.app.parent.my-app-configuration.xyz.bar", + "type": "java.lang.String", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration" + } + ], + "hints": [] +} \ No newline at end of file diff --git a/spring-configuration-property-documenter-core/src/test/resources/regression/spring-configuration-metadata-with-variable-field-name.json b/spring-configuration-property-documenter-core/src/test/resources/regression/spring-configuration-metadata-with-variable-field-name.json new file mode 100644 index 0000000..dcbbe41 --- /dev/null +++ b/spring-configuration-property-documenter-core/src/test/resources/regression/spring-configuration-metadata-with-variable-field-name.json @@ -0,0 +1,34 @@ +{ + "groups": [ + { + "name": "my.app.parent.my-app-configuration", + "type": "com.playground.springplayground.MyApplicationConfiguration", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration" + }, + { + "name": "my.app.parent.my-app-configuration.abcd", + "type": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration", + "sourceMethod": "abcd()" + }, + { + "name": "my.app.parent.my-app-configuration.another-child-config", + "type": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration", + "sourceMethod": "anotherChildConfig()" + } + ], + "properties": [ + { + "name": "my.app.parent.my-app-configuration.abcd.ab", + "type": "java.lang.String", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration" + }, + { + "name": "my.app.parent.my-app-configuration.another-child-config.ab", + "type": "java.lang.String", + "sourceType": "com.playground.springplayground.MyApplicationConfiguration$MyNestedChildConfiguration" + } + ], + "hints": [] +} \ No newline at end of file