Skip to content

Commit 8dd1add

Browse files
Increase code coverage on dynamodb-enhanced module
1 parent 3d03af7 commit 8dd1add

File tree

50 files changed

+7282
-62
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+7282
-62
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
21+
import java.util.List;
22+
import org.apache.logging.log4j.core.LogEvent;
23+
import org.junit.jupiter.api.Test;
24+
import org.slf4j.event.Level;
25+
26+
public class DefaultAttributeConverterProviderTest {
27+
28+
@Test
29+
void findConverter_whenConverterFound_logsConverterFound() {
30+
try (LogCaptor logCaptor = new LogCaptor(DefaultAttributeConverterProvider.class, Level.DEBUG)) {
31+
DefaultAttributeConverterProvider provider = DefaultAttributeConverterProvider.create();
32+
provider.converterFor(EnhancedType.of(String.class));
33+
34+
List<LogEvent> logEvents = logCaptor.loggedEvents();
35+
assertThat(logEvents).hasSize(1);
36+
assertThat(logEvents.get(0).getLevel().name()).isEqualTo(Level.DEBUG.name());
37+
assertThat(logEvents.get(0).getMessage().getFormattedMessage())
38+
.contains("Converter for EnhancedType(java.lang.String): software.amazon.awssdk.enhanced.dynamodb.internal"
39+
+ ".converter.attribute.StringAttributeConverter");
40+
}
41+
}
42+
43+
@Test
44+
void findConverter_whenConverterNotFound_logsNoConverter() {
45+
try (LogCaptor logCaptor = new LogCaptor(DefaultAttributeConverterProvider.class, Level.DEBUG)) {
46+
DefaultAttributeConverterProvider provider = DefaultAttributeConverterProvider.create();
47+
48+
assertThatThrownBy(() -> provider.converterFor(EnhancedType.of(CustomUnsupportedType.class)))
49+
.isInstanceOf(IllegalStateException.class)
50+
.hasMessageContaining("Converter not found for EnhancedType(software.amazon.awssdk.enhanced.dynamodb"
51+
+ ".DefaultAttributeConverterProviderTest$CustomUnsupportedType)");
52+
List<LogEvent> logEvents = logCaptor.loggedEvents();
53+
assertThat(logEvents).hasSize(1);
54+
assertThat(logEvents.get(0).getLevel().name()).isEqualTo(Level.DEBUG.name());
55+
assertThat(logEvents.get(0).getMessage().getFormattedMessage())
56+
.contains("No converter available for EnhancedType(software.amazon.awssdk.enhanced.dynamodb"
57+
+ ".DefaultAttributeConverterProviderTest$CustomUnsupportedType)");
58+
}
59+
}
60+
61+
/**
62+
* A custom type with no converter registered for it.
63+
*/
64+
private static class CustomUnsupportedType {
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb;
17+
18+
import static java.util.stream.Collectors.toList;
19+
import static org.junit.jupiter.api.Assertions.assertThrows;
20+
import static org.mockito.Mockito.CALLS_REAL_METHODS;
21+
import static org.mockito.Mockito.mock;
22+
import static org.mockito.Mockito.when;
23+
24+
import java.io.File;
25+
import java.lang.reflect.Method;
26+
import java.net.URL;
27+
import java.util.Arrays;
28+
import java.util.Collections;
29+
import java.util.List;
30+
import java.util.Optional;
31+
import java.util.function.Consumer;
32+
import java.util.regex.Pattern;
33+
import java.util.stream.Stream;
34+
import org.junit.jupiter.api.DynamicTest;
35+
import org.junit.jupiter.api.TestFactory;
36+
37+
/**
38+
* Test class that discovers all interfaces with default methods that throw UnsupportedOperationException. Shows individual test
39+
* scenarios and results using DynamicTest.
40+
*/
41+
public class DefaultMethodsUnsupportedOperationTest {
42+
43+
private static final String BASE_PACKAGE = "software.amazon.awssdk.enhanced.dynamodb";
44+
private static final Pattern CLASS_PATTERN = Pattern.compile(".class", Pattern.LITERAL);
45+
46+
private static final List<String> testScenarios = Collections.synchronizedList(new java.util.ArrayList<>());
47+
48+
@TestFactory
49+
Stream<DynamicTest> testDefaultMethodsThrowUnsupportedOperation() {
50+
return scanPackageForClasses(BASE_PACKAGE)
51+
.filter(Class::isInterface)
52+
.filter(this::hasDefaultMethods)
53+
.collect(toList())
54+
.stream()
55+
.flatMap(this::createTestsForInterface)
56+
.collect(toList())
57+
.stream();
58+
}
59+
60+
private Stream<Class<?>> scanPackageForClasses(String packageName) {
61+
try {
62+
ClassLoader loader = Thread.currentThread().getContextClassLoader();
63+
return Collections.list(loader.getResources(packageName.replace('.', '/')))
64+
.stream()
65+
.map(URL::getFile)
66+
.map(File::new)
67+
.filter(File::exists)
68+
.flatMap(dir -> findClassesInDirectory(dir, packageName));
69+
} catch (Exception e) {
70+
return Stream.empty();
71+
}
72+
}
73+
74+
private Stream<Class<?>> findClassesInDirectory(File dir, String packageName) {
75+
return Optional.ofNullable(dir.listFiles())
76+
.map(Arrays::stream)
77+
.orElseGet(Stream::empty)
78+
.flatMap(file ->
79+
file.isDirectory()
80+
? findClassesInDirectory(file, packageName + "." + file.getName())
81+
: loadClassFromFile(file, packageName));
82+
}
83+
84+
private Stream<Class<?>> loadClassFromFile(File file, String packageName) {
85+
if (!file.getName().endsWith(".class")) {
86+
return Stream.empty();
87+
}
88+
89+
String className = packageName + '.' + CLASS_PATTERN.matcher(file.getName()).replaceAll("");
90+
try {
91+
return Stream.of(Class.forName(className));
92+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
93+
return Stream.empty();
94+
}
95+
}
96+
97+
private boolean hasDefaultMethods(Class<?> interfaceClass) {
98+
return Arrays.stream(interfaceClass.getDeclaredMethods())
99+
.anyMatch(Method::isDefault);
100+
}
101+
102+
private Stream<DynamicTest> createTestsForInterface(Class<?> interfaceClass) {
103+
return Arrays.stream(interfaceClass.getDeclaredMethods())
104+
.filter(Method::isDefault)
105+
.filter(method -> throwsUnsupportedOperation(interfaceClass, method))
106+
.map(method -> {
107+
String testName = String.format("%s.%s() → throws UnsupportedOperationException",
108+
interfaceClass.getSimpleName(),
109+
method.getName());
110+
testScenarios.add(testName);
111+
112+
return DynamicTest.dynamicTest(testName, () ->
113+
testMethodThrowsUnsupportedOperation(interfaceClass, method));
114+
});
115+
}
116+
117+
private boolean throwsUnsupportedOperation(Class<?> interfaceClass, Method method) {
118+
try {
119+
Object mockInstance = createMockInstance(interfaceClass);
120+
Object[] args = createArguments(method);
121+
method.invoke(mockInstance, args);
122+
return false;
123+
} catch (Exception e) {
124+
Throwable cause = e.getCause() != null ? e.getCause() : e;
125+
return cause instanceof UnsupportedOperationException;
126+
}
127+
}
128+
129+
private void testMethodThrowsUnsupportedOperation(Class<?> interfaceClass, Method method) {
130+
Object mockInstance = createMockInstance(interfaceClass);
131+
Object[] args = createArguments(method);
132+
133+
assertThrows(UnsupportedOperationException.class, () -> {
134+
try {
135+
method.invoke(mockInstance, args);
136+
} catch (Exception e) {
137+
Throwable cause = e.getCause() != null ? e.getCause() : e;
138+
if (cause instanceof UnsupportedOperationException) {
139+
throw cause;
140+
}
141+
throw new RuntimeException(cause);
142+
}
143+
}, () -> String.format("Expected %s.%s() to throw UnsupportedOperationException",
144+
interfaceClass.getSimpleName(), method.getName()));
145+
}
146+
147+
private <T> T createMockInstance(Class<T> interfaceClass) {
148+
T mock = mock(interfaceClass, CALLS_REAL_METHODS);
149+
if (mock instanceof MappedTableResource) {
150+
when(((MappedTableResource<?>) mock).tableName()).thenReturn("test-table");
151+
}
152+
return mock;
153+
}
154+
155+
private Object[] createArguments(Method method) {
156+
return Arrays.stream(method.getParameterTypes()).map(this::createArgument).toArray();
157+
}
158+
159+
private Object createArgument(Class<?> paramType) {
160+
if (paramType == String.class) {
161+
return "test";
162+
}
163+
if (paramType == Key.class) {
164+
return Key.builder().partitionValue("test").build();
165+
}
166+
if (Consumer.class.isAssignableFrom(paramType)) {
167+
return (Consumer<?>) obj -> {
168+
};
169+
}
170+
if (paramType.isInterface()) {
171+
return mock(paramType);
172+
}
173+
try {
174+
return mock(paramType);
175+
} catch (Exception e) {
176+
return null;
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)