|
5 | 5 |
|
6 | 6 | import com.azure.spring.cloud.core.implementation.factory.AzureServiceClientBuilderFactory; |
7 | 7 | import com.azure.spring.cloud.core.implementation.properties.AzureSdkProperties; |
| 8 | +import org.junit.jupiter.api.Test; |
8 | 9 |
|
9 | | -public abstract class AzureGenericServiceClientBuilderFactoryBaseTests<P extends AzureSdkProperties, F extends AzureServiceClientBuilderFactory<?>> { |
| 10 | +import java.lang.reflect.Method; |
| 11 | +import java.lang.reflect.Parameter; |
| 12 | +import java.time.Duration; |
| 13 | +import java.util.Arrays; |
| 14 | +import java.util.HashSet; |
| 15 | +import java.util.Map; |
| 16 | +import java.util.Set; |
| 17 | +import java.util.function.Consumer; |
| 18 | +import java.util.function.Function; |
| 19 | +import java.util.function.Predicate; |
| 20 | +import java.util.stream.Collectors; |
| 21 | + |
| 22 | +import static com.azure.spring.cloud.core.implementation.util.ClassUtils.isPrimitive; |
| 23 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
| 24 | + |
| 25 | +public abstract class AzureGenericServiceClientBuilderFactoryBaseTests<P extends AzureSdkProperties, |
| 26 | + F extends AzureServiceClientBuilderFactory<?>> { |
10 | 27 |
|
11 | 28 | protected abstract P createMinimalServiceProperties(); |
| 29 | + |
12 | 30 | protected abstract F createClientBuilderFactoryWithMockBuilder(P properties); |
13 | 31 |
|
| 32 | + private static final Set<Class<?>> IGNORED_CLASS = Set.of(Object.class, Class.class, Enum.class, String.class, |
| 33 | + Boolean.class, Integer.class, Long.class, Duration.class); |
| 34 | + private static final Set<String> IGNORED_METHOD_NAMES = |
| 35 | + Arrays.stream(Object.class.getMethods()).map(Method::getName).collect(Collectors.toSet()); |
| 36 | + private static final Set<Class<?>> BUILDER_IGNORED_PARAMETER_TYPES = Set.of(Consumer.class); |
| 37 | + private static final Set<String> BUILDER_IGNORED_METHOD_NAME_PREFIX = Set.of("build", "process"); |
| 38 | + private static final Function<String, String> EXTRACT_METHOD_NAME = methodName -> { |
| 39 | + if (methodName.startsWith("is")) { |
| 40 | + return methodName.substring("is".length()); |
| 41 | + } else { |
| 42 | + return methodName.substring("set".length()); |
| 43 | + } |
| 44 | + }; |
| 45 | + |
| 46 | + public static Set<String> listSupportedProperties(Class<?> propertiesClass) { |
| 47 | + Set<Method> classMethodSet = new HashSet<>(); |
| 48 | + listClassMethods(classMethodSet, method -> Set.of(method.getReturnType()), propertiesClass, |
| 49 | + method -> method.getName().startsWith("is") || method.getName().startsWith("get")); |
| 50 | + return classMethodSet.stream() |
| 51 | + .map(Method::getName) |
| 52 | + .map(EXTRACT_METHOD_NAME) |
| 53 | + .collect(Collectors.toSet()); |
| 54 | + } |
| 55 | + |
| 56 | + public static Set<String> listBuilderProperties(Class<?> builderClass) { |
| 57 | + Set<Method> classMethodSet = new HashSet<>(); |
| 58 | + listClassMethods(classMethodSet, |
| 59 | + method -> Arrays.stream(method.getParameters()) |
| 60 | + .map(Parameter::getType) |
| 61 | + .filter(type -> !BUILDER_IGNORED_PARAMETER_TYPES.contains(type)) |
| 62 | + .collect(Collectors.toSet()), |
| 63 | + builderClass, method -> BUILDER_IGNORED_METHOD_NAME_PREFIX.stream() |
| 64 | + .noneMatch(prefix -> method.getName().startsWith(prefix))); |
| 65 | + return classMethodSet.stream().map(Method::getName).collect(Collectors.toSet()); |
| 66 | + } |
| 67 | + |
| 68 | + public static void listClassMethods(Set<Method> classMethodSet, Function<Method, Set<Class<?>>> iterationType, |
| 69 | + Class<?> propertiesClass, Predicate<Method> filter) { |
| 70 | + if (isPrimitive(propertiesClass) || propertiesClass.isEnum() || IGNORED_CLASS.contains(propertiesClass)) { |
| 71 | + return; |
| 72 | + } |
| 73 | + |
| 74 | + Method[] propertiesMethods = propertiesClass.getMethods(); |
| 75 | + Set<Method> methodSet = Arrays.stream(propertiesMethods) |
| 76 | + .filter(filter) |
| 77 | + .filter(method -> !IGNORED_METHOD_NAMES.contains(method.getName())) |
| 78 | + .collect(Collectors.toSet()); |
| 79 | + if (iterationType != null) { |
| 80 | + methodSet.forEach(method -> { |
| 81 | + for (Class<?> subClass : iterationType.apply(method)) { |
| 82 | + listClassMethods(classMethodSet, iterationType, subClass, filter); |
| 83 | + } |
| 84 | + }); |
| 85 | + } |
| 86 | + System.out.println("[" + propertiesClass.getSimpleName() + "] class property names: \n" |
| 87 | + + methodSet.stream().map(Method::getName).map(EXTRACT_METHOD_NAME).collect(Collectors.toSet())); |
| 88 | + classMethodSet.addAll(methodSet); |
| 89 | + |
| 90 | + Class<?> superclass = propertiesClass.getSuperclass(); |
| 91 | + if (superclass != null) { |
| 92 | + listClassMethods(classMethodSet, iterationType, superclass, filter); |
| 93 | + } |
| 94 | + } |
| 95 | + |
14 | 96 | protected F factoryWithMinimalSettings() { |
15 | 97 | P properties = createMinimalServiceProperties(); |
16 | 98 | return createClientBuilderFactoryWithMockBuilder(properties); |
@@ -64,4 +146,44 @@ private P createManagedIdentityCredentialAwareServiceProperties(P properties) { |
64 | 146 | return properties; |
65 | 147 | } |
66 | 148 |
|
| 149 | + @Test |
| 150 | + void supportSdkBuilderAllProperties() { |
| 151 | + verifyNoUnsupportedPropertiesFromBuilderClass(); |
| 152 | + } |
| 153 | + |
| 154 | + public PropertiesIntegrityParameters getParametersForPropertiesIntegrity() { |
| 155 | + // override by sub builder factory class |
| 156 | + return null; |
| 157 | + } |
| 158 | + |
| 159 | + public void verifyNoUnsupportedPropertiesFromBuilderClass() { |
| 160 | + PropertiesIntegrityParameters parameters = getParametersForPropertiesIntegrity(); |
| 161 | + if (parameters == null) { |
| 162 | + return; |
| 163 | + } |
| 164 | + |
| 165 | + Set<String> supportedProperties = listSupportedProperties(parameters.propertiesClass()); |
| 166 | + Set<String> builderProperties = listBuilderProperties(parameters.sdkBinderClass()); |
| 167 | + Set<String> lowCaseSupportedProperties = |
| 168 | + supportedProperties.stream().map(String::toLowerCase).collect(Collectors.toSet()); |
| 169 | + Map<String, String> namingFromBinderToProperties = parameters.propertyNameMappingForBinder(); |
| 170 | + Set<String> unsupportedPropertyNames = |
| 171 | + builderProperties.stream() |
| 172 | + .map(String::toLowerCase) |
| 173 | + .filter(builderPropertyName -> { |
| 174 | + String targetName = builderPropertyName.toLowerCase(); |
| 175 | + return (!lowCaseSupportedProperties.contains(targetName) && !namingFromBinderToProperties.containsKey(targetName)) |
| 176 | + || (namingFromBinderToProperties.containsKey(targetName) && !lowCaseSupportedProperties.contains(namingFromBinderToProperties.get(targetName))); |
| 177 | + }) |
| 178 | + .collect(Collectors.toSet()); |
| 179 | + System.out.println("Properties class supported property names: \n" + supportedProperties); |
| 180 | + System.out.println("Builder class owned property names: \n" + builderProperties); |
| 181 | + System.out.println("Unsupported property names: \n" + unsupportedPropertyNames); |
| 182 | + assertTrue(unsupportedPropertyNames.isEmpty()); |
| 183 | + } |
| 184 | + |
| 185 | + public record PropertiesIntegrityParameters(Class<?> propertiesClass, Class<?> sdkBinderClass, |
| 186 | + Map<String, String> propertyNameMappingForBinder) { |
| 187 | + |
| 188 | + } |
67 | 189 | } |
0 commit comments