Skip to content

Commit 3189683

Browse files
Increase code coverage on dynamodb-enhanced module (#6969)
* Increase code coverage on dynamodb-enhanced module * Addressing comments (refactoring, removing duplicated tests). * Addressing comments (adding more tests for chain extension). * Adding release change note. * Parametrization of annotated bean and immutable table schema tests. Adding assert for OptionalAttributeValueConverterTest. * Refactoring test class: AutoGeneratedUuidExtensionTest * Removing test class: AutoGeneratedUuidExtensionTest... will be added as part of PR 6373 * Removing unused utils class for tests. * Fixing conflict and updated tests. * Fixing conflicts on EnhancedClientUtilsTest * Removing duplicate test pairs. * Replacing hamcrest imports with assertj. * Tests refactorization (parameterization). * Fix build failures * Fix tests --------- Co-authored-by: andreasgrafenberger <andreas.grafenberger@endava.com>
1 parent faf0ded commit 3189683

50 files changed

Lines changed: 6317 additions & 810 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "Amazon DynamoDB Enhanced Client",
4+
"contributor": "",
5+
"description": "Increase code coverage on dynamodb-enhanced module"
6+
}

services-custom/dynamodb-enhanced/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@
208208
<artifactId>DynamoDBLocal</artifactId>
209209
<scope>test</scope>
210210
</dependency>
211+
<dependency>
212+
<groupId>org.slf4j</groupId>
213+
<artifactId>slf4j-api</artifactId>
214+
<scope>test</scope>
215+
</dependency>
211216
<dependency>
212217
<groupId>com.almworks.sqlite4java</groupId>
213218
<artifactId>sqlite4java</artifactId>
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,181 @@
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.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
import static org.mockito.Mockito.CALLS_REAL_METHODS;
22+
import static org.mockito.Mockito.mock;
23+
import static org.mockito.Mockito.when;
24+
25+
import java.io.File;
26+
import java.lang.reflect.Method;
27+
import java.net.URL;
28+
import java.util.Arrays;
29+
import java.util.Collections;
30+
import java.util.List;
31+
import java.util.Optional;
32+
import java.util.function.Consumer;
33+
import java.util.regex.Pattern;
34+
import java.util.stream.Stream;
35+
import org.junit.jupiter.api.DynamicTest;
36+
import org.junit.jupiter.api.TestFactory;
37+
38+
/**
39+
* Test class that discovers all interfaces with default methods that throw UnsupportedOperationException. Shows individual test
40+
* scenarios and results using DynamicTest.
41+
*/
42+
public class DefaultMethodsUnsupportedOperationTest {
43+
44+
private static final String BASE_PACKAGE = "software.amazon.awssdk.enhanced.dynamodb";
45+
private static final Pattern CLASS_PATTERN = Pattern.compile(".class", Pattern.LITERAL);
46+
47+
private static final List<String> testScenarios = Collections.synchronizedList(new java.util.ArrayList<>());
48+
49+
@TestFactory
50+
Stream<DynamicTest> testDefaultMethodsThrowUnsupportedOperation() {
51+
List<DynamicTest> dynamicTestList = scanPackageForClasses(BASE_PACKAGE)
52+
.filter(Class::isInterface)
53+
.filter(this::hasDefaultMethods)
54+
.collect(toList())
55+
.stream()
56+
.flatMap(this::createTestsForInterface)
57+
.collect(toList());
58+
assertEquals(102, dynamicTestList.size());
59+
return dynamicTestList.stream();
60+
}
61+
62+
private Stream<Class<?>> scanPackageForClasses(String packageName) {
63+
try {
64+
ClassLoader loader = Thread.currentThread().getContextClassLoader();
65+
return Collections.list(loader.getResources(packageName.replace('.', '/')))
66+
.stream()
67+
.map(URL::getFile)
68+
.map(File::new)
69+
.filter(File::exists)
70+
.flatMap(dir -> findClassesInDirectory(dir, packageName));
71+
} catch (Exception e) {
72+
return Stream.empty();
73+
}
74+
}
75+
76+
private Stream<Class<?>> findClassesInDirectory(File dir, String packageName) {
77+
return Optional.ofNullable(dir.listFiles())
78+
.map(Arrays::stream)
79+
.orElseGet(Stream::empty)
80+
.flatMap(file ->
81+
file.isDirectory()
82+
? findClassesInDirectory(file, packageName + "." + file.getName())
83+
: loadClassFromFile(file, packageName));
84+
}
85+
86+
private Stream<Class<?>> loadClassFromFile(File file, String packageName) {
87+
if (!file.getName().endsWith(".class")) {
88+
return Stream.empty();
89+
}
90+
91+
String className = packageName + '.' + CLASS_PATTERN.matcher(file.getName()).replaceAll("");
92+
try {
93+
return Stream.of(Class.forName(className));
94+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
95+
return Stream.empty();
96+
}
97+
}
98+
99+
private boolean hasDefaultMethods(Class<?> interfaceClass) {
100+
return Arrays.stream(interfaceClass.getDeclaredMethods())
101+
.anyMatch(Method::isDefault);
102+
}
103+
104+
private Stream<DynamicTest> createTestsForInterface(Class<?> interfaceClass) {
105+
return Arrays.stream(interfaceClass.getDeclaredMethods())
106+
.filter(Method::isDefault)
107+
.filter(method -> throwsUnsupportedOperation(interfaceClass, method))
108+
.map(method -> {
109+
String testName = String.format("%s.%s() → throws UnsupportedOperationException",
110+
interfaceClass.getSimpleName(),
111+
method.getName());
112+
testScenarios.add(testName);
113+
114+
return DynamicTest.dynamicTest(testName, () ->
115+
testMethodThrowsUnsupportedOperation(interfaceClass, method));
116+
});
117+
}
118+
119+
private boolean throwsUnsupportedOperation(Class<?> interfaceClass, Method method) {
120+
try {
121+
Object mockInstance = createMockInstance(interfaceClass);
122+
Object[] args = createArguments(method);
123+
method.invoke(mockInstance, args);
124+
return false;
125+
} catch (Exception e) {
126+
Throwable cause = e.getCause() != null ? e.getCause() : e;
127+
return cause instanceof UnsupportedOperationException;
128+
}
129+
}
130+
131+
private void testMethodThrowsUnsupportedOperation(Class<?> interfaceClass, Method method) {
132+
Object mockInstance = createMockInstance(interfaceClass);
133+
Object[] args = createArguments(method);
134+
135+
assertThrows(UnsupportedOperationException.class, () -> {
136+
try {
137+
method.invoke(mockInstance, args);
138+
} catch (Exception e) {
139+
Throwable cause = e.getCause() != null ? e.getCause() : e;
140+
if (cause instanceof UnsupportedOperationException) {
141+
throw cause;
142+
}
143+
throw new RuntimeException(cause);
144+
}
145+
}, () -> String.format("Expected %s.%s() to throw UnsupportedOperationException",
146+
interfaceClass.getSimpleName(), method.getName()));
147+
}
148+
149+
private <T> T createMockInstance(Class<T> interfaceClass) {
150+
T mock = mock(interfaceClass, CALLS_REAL_METHODS);
151+
if (mock instanceof MappedTableResource) {
152+
when(((MappedTableResource<?>) mock).tableName()).thenReturn("test-table");
153+
}
154+
return mock;
155+
}
156+
157+
private Object[] createArguments(Method method) {
158+
return Arrays.stream(method.getParameterTypes()).map(this::createArgument).toArray();
159+
}
160+
161+
private Object createArgument(Class<?> paramType) {
162+
if (paramType == String.class) {
163+
return "test";
164+
}
165+
if (paramType == Key.class) {
166+
return Key.builder().partitionValue("test").build();
167+
}
168+
if (Consumer.class.isAssignableFrom(paramType)) {
169+
return (Consumer<?>) obj -> {
170+
};
171+
}
172+
if (paramType.isInterface()) {
173+
return mock(paramType);
174+
}
175+
try {
176+
return mock(paramType);
177+
} catch (Exception e) {
178+
return null;
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)