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.document;
17 
18 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
19 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
20 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
21 import static org.assertj.core.api.Assertions.assertThatNullPointerException;
22 import static software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider.defaultProvider;
23 import static org.assertj.core.api.Assertions.assertThat;
24 import static software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocumentTestData.testDataInstance;
25 
26 import com.fasterxml.jackson.core.JsonProcessingException;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import java.math.BigDecimal;
29 import java.time.LocalDate;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.LinkedHashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.UUID;
38 import java.util.stream.Collectors;
39 import java.util.stream.Stream;
40 import org.junit.jupiter.api.Assertions;
41 import org.junit.jupiter.api.Test;
42 import org.junit.jupiter.params.ParameterizedTest;
43 import org.junit.jupiter.params.provider.Arguments;
44 import org.junit.jupiter.params.provider.MethodSource;
45 import org.junit.jupiter.params.provider.ValueSource;
46 import software.amazon.awssdk.core.SdkBytes;
47 import software.amazon.awssdk.core.SdkNumber;
48 import software.amazon.awssdk.enhanced.dynamodb.DefaultAttributeConverterProvider;
49 import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
50 import software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomAttributeForDocumentConverterProvider;
51 import software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomClassForDocumentAPI;
52 
53 class EnhancedDocumentTest {
54 
escapeDocumentStrings()55     private static Stream<Arguments> escapeDocumentStrings() {
56         char c = 0x0a;
57         return Stream.of(
58             Arguments.of(String.valueOf(c), "{\"key\":\"\\n\"}")
59             , Arguments.of("", "{\"key\":\"\"}")
60             , Arguments.of("\"", "{\"key\":\"\\\"\"}")
61             , Arguments.of("\\", "{\"key\":\"\\\\\"}")
62             , Arguments.of(" ", "{\"key\":\" \"}")
63             , Arguments.of("\t", "{\"key\":\"\\t\"}")
64             , Arguments.of("\n", "{\"key\":\"\\n\"}")
65             , Arguments.of("\r", "{\"key\":\"\\r\"}")
66             , Arguments.of("\f", "{\"key\":\"\\f\"}")
67         );
68     }
69 
unEscapeDocumentStrings()70     private static Stream<Arguments> unEscapeDocumentStrings() {
71         return Stream.of(
72             Arguments.of("'", "{\"key\":\"'\"}"),
73             Arguments.of("'single quote'", "{\"key\":\"'single quote'\"}")
74         );
75     }
76 
77     @Test
enhancedDocumentGetters()78     void enhancedDocumentGetters() {
79 
80         EnhancedDocument document = testDataInstance()
81             .dataForScenario("complexDocWithSdkBytesAndMapArrays_And_PutOverWritten")
82             .getEnhancedDocument();
83         // Assert
84         assertThat(document.getString("stringKey")).isEqualTo("stringValue");
85         assertThat(document.getNumber("numberKey")).isEqualTo(SdkNumber.fromInteger(1));
86         assertThat(document.getList("numberList", EnhancedType.of(BigDecimal.class)))
87             .containsExactly(BigDecimal.valueOf(4), BigDecimal.valueOf(5), BigDecimal.valueOf(6));
88         assertThat(document.getList("numberList", EnhancedType.of(SdkNumber.class)))
89             .containsExactly(SdkNumber.fromInteger(4), SdkNumber.fromInteger(5), SdkNumber.fromInteger(6));
90         assertThat(document.get("simpleDate", EnhancedType.of(LocalDate.class))).isEqualTo(LocalDate.MIN);
91         assertThat(document.getStringSet("stringSet")).containsExactly("one", "two");
92         assertThat(document.getBytes("sdkByteKey")).isEqualTo(SdkBytes.fromUtf8String("a"));
93         assertThat(document.getBytesSet("sdkByteSet"))
94             .containsExactlyInAnyOrder(SdkBytes.fromUtf8String("a"), SdkBytes.fromUtf8String("b"));
95         assertThat(document.getNumberSet("numberSetSet")).containsExactlyInAnyOrder(SdkNumber.fromInteger(1),
96                                                                                     SdkNumber.fromInteger(2));
97 
98         Map<String, BigDecimal> expectedBigDecimalMap = new LinkedHashMap<>();
99         expectedBigDecimalMap.put("78b3522c-2ab3-4162-8c5d-f093fa76e68c", BigDecimal.valueOf(3));
100         expectedBigDecimalMap.put("4ae1f694-52ce-4cf6-8211-232ccf780da8", BigDecimal.valueOf(9));
101         assertThat(document.getMap("simpleMap", EnhancedType.of(String.class), EnhancedType.of(BigDecimal.class)))
102             .containsExactlyEntriesOf(expectedBigDecimalMap);
103 
104         Map<UUID, BigDecimal> expectedUuidBigDecimalMap = new LinkedHashMap<>();
105         expectedUuidBigDecimalMap.put(UUID.fromString("78b3522c-2ab3-4162-8c5d-f093fa76e68c"), BigDecimal.valueOf(3));
106         expectedUuidBigDecimalMap.put(UUID.fromString("4ae1f694-52ce-4cf6-8211-232ccf780da8"), BigDecimal.valueOf(9));
107         assertThat(document.getMap("simpleMap", EnhancedType.of(UUID.class), EnhancedType.of(BigDecimal.class)))
108             .containsExactlyEntriesOf(expectedUuidBigDecimalMap);
109     }
110 
111     @Test
enhancedDocWithNestedListAndMaps()112     void enhancedDocWithNestedListAndMaps() {
113         /**
114          * No attributeConverters supplied, in this case it uses the {@link DefaultAttributeConverterProvider} and does not error
115          */
116         EnhancedDocument simpleDoc = EnhancedDocument.builder()
117                                                      .attributeConverterProviders(defaultProvider())
118                                                      .putString("HashKey", "abcdefg123")
119                                                      .putNull("nullKey")
120                                                      .putNumber("numberKey", 2.0)
121                                                      .putBytes("sdkByte", SdkBytes.fromUtf8String("a"))
122                                                      .putBoolean("booleanKey", true)
123                                                      .putJson("jsonKey", "{\"1\": [\"a\", \"b\", \"c\"],\"2\": 1}")
124                                                      .putStringSet("stingSet",
125                                                                    Stream.of("a", "b", "c").collect(Collectors.toSet()))
126 
127                                                      .putNumberSet("numberSet", Stream.of(1, 2, 3, 4).collect(Collectors.toSet()))
128                                                      .putBytesSet("sdkByteSet",
129                                                                   Stream.of(SdkBytes.fromUtf8String("a")).collect(Collectors.toSet()))
130                                                      .build();
131 
132         assertThat(simpleDoc.toJson()).isEqualTo("{\"HashKey\":\"abcdefg123\",\"nullKey\":null,\"numberKey\":2.0,"
133                                                  + "\"sdkByte\":\"YQ==\",\"booleanKey\":true,\"jsonKey\":{\"1\":[\"a\",\"b\","
134                                                  + "\"c\"],\"2\":1},\"stingSet\":[\"a\",\"b\",\"c\"],\"numberSet\":[1,2,3,4],"
135                                                  + "\"sdkByteSet\":[\"YQ==\"]}");
136 
137 
138         assertThat(simpleDoc.isPresent("HashKey")).isTrue();
139         // No Null pointer or doesnot exist is thrown
140         assertThat(simpleDoc.isPresent("HashKey2")).isFalse();
141         assertThat(simpleDoc.getString("HashKey")).isEqualTo("abcdefg123");
142         assertThat(simpleDoc.isNull("nullKey")).isTrue();
143 
144         assertThat(simpleDoc.getNumber("numberKey")).isEqualTo(SdkNumber.fromDouble(2.0));
145         assertThat(simpleDoc.getNumber("numberKey").bigDecimalValue().compareTo(BigDecimal.valueOf(2.0))).isEqualTo(0);
146 
147         assertThat(simpleDoc.getBytes("sdkByte")).isEqualTo(SdkBytes.fromUtf8String("a"));
148         assertThat(simpleDoc.getBoolean("booleanKey")).isTrue();
149         assertThat(simpleDoc.getJson("jsonKey")).isEqualTo("{\"1\":[\"a\",\"b\",\"c\"],\"2\":1}");
150         assertThat(simpleDoc.getStringSet("stingSet")).isEqualTo(Stream.of("a", "b", "c").collect(Collectors.toSet()));
151         assertThat(simpleDoc.getList("stingSet", EnhancedType.of(String.class))).isEqualTo(Stream.of("a", "b", "c").collect(Collectors.toList()));
152 
153         assertThat(simpleDoc.getNumberSet("numberSet")
154                             .stream().map(n -> n.intValue()).collect(Collectors.toSet()))
155             .isEqualTo(Stream.of(1, 2, 3, 4).collect(Collectors.toSet()));
156 
157 
158         assertThat(simpleDoc.getBytesSet("sdkByteSet")).isEqualTo(Stream.of(SdkBytes.fromUtf8String("a")).collect(Collectors.toSet()));
159 
160 
161         // Trying to access some other Types
162         assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> simpleDoc.getBoolean("sdkByteSet"))
163                                                               .withMessageContaining("BooleanAttributeConverter cannot convert "
164                                                                                      + "an attribute of type BS into the requested type class java.lang.Boolean");
165 
166 
167     }
168 
169     @Test
testNullArgsInStaticConstructor()170     void testNullArgsInStaticConstructor() {
171         assertThatNullPointerException()
172             .isThrownBy(() -> EnhancedDocument.fromAttributeValueMap(null))
173             .withMessage("attributeValueMap must not be null.");
174 
175         assertThatNullPointerException()
176             .isThrownBy(() -> EnhancedDocument.fromJson(null))
177             .withMessage("json must not be null.");
178     }
179 
180     @Test
accessingSetFromBuilderMethodsAsListsInDocuments()181     void accessingSetFromBuilderMethodsAsListsInDocuments() {
182         Set<String> stringSet = Stream.of("a", "b", "c").collect(Collectors.toSet());
183 
184         EnhancedDocument enhancedDocument = EnhancedDocument.builder()
185                                                             .addAttributeConverterProvider(defaultProvider())
186                                                             .putStringSet("stringSet", stringSet)
187                                                             .build();
188 
189         Set<String> retrievedStringSet = enhancedDocument.getStringSet("stringSet");
190         assertThat(retrievedStringSet).isEqualTo(stringSet);
191         // Note that this behaviour is different in V1 , in order to remain consistent with EnhancedDDB converters
192         List<String> retrievedStringList = enhancedDocument.getList("stringSet", EnhancedType.of(String.class));
193         assertThat(retrievedStringList).containsExactlyInAnyOrderElementsOf(stringSet);
194     }
195 
196     @Test
builder_ResetsTheOldValues_beforeJsonSetterIsCalled()197     void builder_ResetsTheOldValues_beforeJsonSetterIsCalled() {
198 
199         EnhancedDocument enhancedDocument = EnhancedDocument.builder()
200                                                             .attributeConverterProviders(defaultProvider())
201                                                             .putString("simpleKeyOriginal", "simpleValueOld")
202                                                             .json("{\"stringKey\": \"stringValue\"}")
203                                                             .putString("simpleKeyNew", "simpleValueNew")
204                                                             .build();
205 
206         assertThat(enhancedDocument.toJson()).isEqualTo("{\"stringKey\":\"stringValue\",\"simpleKeyNew\":\"simpleValueNew\"}");
207         assertThat(enhancedDocument.getString("simpleKeyOriginal")).isNull();
208 
209     }
210 
211     @Test
builder_with_NullKeys()212     void builder_with_NullKeys() {
213         String EMPTY_OR_NULL_ERROR = "Attribute name must not be null or empty.";
214         assertThatIllegalArgumentException()
215             .isThrownBy(() -> EnhancedDocument.builder().putString(null, "Sample"))
216             .withMessage(EMPTY_OR_NULL_ERROR);
217 
218         assertThatIllegalArgumentException()
219             .isThrownBy(() -> EnhancedDocument.builder().putNull(null))
220             .withMessage(EMPTY_OR_NULL_ERROR);
221 
222         assertThatIllegalArgumentException()
223             .isThrownBy(() -> EnhancedDocument.builder().putNumber(null, 3))
224             .withMessage(EMPTY_OR_NULL_ERROR);
225 
226         assertThatIllegalArgumentException()
227             .isThrownBy(() -> EnhancedDocument.builder().putList(null, Arrays.asList(), EnhancedType.of(String.class)))
228             .withMessage(EMPTY_OR_NULL_ERROR);
229 
230         assertThatIllegalArgumentException()
231             .isThrownBy(() -> EnhancedDocument.builder().putBytes(null, SdkBytes.fromUtf8String("a")))
232             .withMessage(EMPTY_OR_NULL_ERROR);
233 
234         assertThatIllegalArgumentException()
235             .isThrownBy(() -> EnhancedDocument.builder().putMap(null, new HashMap<>(), null, null))
236             .withMessage(EMPTY_OR_NULL_ERROR);
237 
238         assertThatIllegalArgumentException()
239             .isThrownBy(() -> EnhancedDocument.builder().putStringSet(null, Stream.of("a").collect(Collectors.toSet())))
240             .withMessage(EMPTY_OR_NULL_ERROR);
241 
242         assertThatIllegalArgumentException()
243             .isThrownBy(() -> EnhancedDocument.builder().putNumberSet(null, Stream.of(1).collect(Collectors.toSet())))
244             .withMessage(EMPTY_OR_NULL_ERROR);
245         assertThatIllegalArgumentException()
246             .isThrownBy(() -> EnhancedDocument.builder().putStringSet(null, Stream.of("a").collect(Collectors.toSet())))
247             .withMessage(EMPTY_OR_NULL_ERROR);
248         assertThatIllegalArgumentException()
249             .isThrownBy(() -> EnhancedDocument.builder().putBytesSet(null, Stream.of(SdkBytes.fromUtf8String("a"))
250                                                                                  .collect(Collectors.toSet())))
251             .withMessage(EMPTY_OR_NULL_ERROR);
252     }
253 
254     @Test
errorWhen_NoAttributeConverter_IsProviderIsDefined()255     void errorWhen_NoAttributeConverter_IsProviderIsDefined() {
256         EnhancedDocument enhancedDocument = testDataInstance().dataForScenario("simpleString")
257                                                               .getEnhancedDocument()
258                                                               .toBuilder()
259                                                               .attributeConverterProviders(CustomAttributeForDocumentConverterProvider.create())
260                                                               .build();
261 
262         EnhancedType getType = EnhancedType.of(EnhancedDocumentTestData.class);
263         assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> enhancedDocument.get(
264             "stringKey", getType
265         )).withMessage(
266             "AttributeConverter not found for class EnhancedType(java.lang.String). Please add an AttributeConverterProvider "
267             + "for this type. "
268             + "If it is a default type, add the DefaultAttributeConverterProvider to the builder.");
269     }
270 
271     @Test
access_NumberAttributeFromMap()272     void access_NumberAttributeFromMap() {
273         EnhancedDocument enhancedDocument = EnhancedDocument.fromJson(testDataInstance()
274                                                                           .dataForScenario("ElementsOfCustomType")
275                                                                           .getJson());
276 
277         assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() ->
278                                                                               enhancedDocument.getNumber("customMapValue"))
279                                                               .withMessage(
280                                                                   "software.amazon.awssdk.enhanced.dynamodb.internal.converter"
281                                                                   + ".attribute.SdkNumberAttributeConverter cannot convert"
282                                                                   + " an attribute of type M into the requested type class "
283                                                                   + "software.amazon.awssdk.core.SdkNumber");
284     }
285 
286     @Test
access_CustomType_without_AttributeConverterProvider()287     void access_CustomType_without_AttributeConverterProvider() {
288         EnhancedDocument enhancedDocument = EnhancedDocument.fromJson(testDataInstance()
289                                                                           .dataForScenario("ElementsOfCustomType")
290                                                                           .getJson());
291 
292         EnhancedType enhancedType = EnhancedType.of(
293             CustomClassForDocumentAPI.class);
294 
295         assertThatExceptionOfType(IllegalStateException.class).isThrownBy(
296             () -> enhancedDocument.get(
297                 "customMapValue", enhancedType)).withMessage("Converter not found for "
298                                                              + "EnhancedType(software.amazon.awssdk.enhanced.dynamodb.converters"
299                                                              + ".document.CustomClassForDocumentAPI)");
300         EnhancedDocument docWithCustomProvider =
301             enhancedDocument.toBuilder().attributeConverterProviders(CustomAttributeForDocumentConverterProvider.create(),
302                                                                      defaultProvider()).build();
303         assertThat(docWithCustomProvider.get("customMapValue", EnhancedType.of(CustomClassForDocumentAPI.class))).isNotNull();
304     }
305 
306     @Test
error_When_DefaultProviderIsPlacedCustomProvider()307     void error_When_DefaultProviderIsPlacedCustomProvider() {
308         CustomClassForDocumentAPI customObject = CustomClassForDocumentAPI.builder().string("str_one")
309                                                                           .longNumber(26L)
310                                                                           .aBoolean(false).build();
311         EnhancedDocument afterCustomClass = EnhancedDocument.builder()
312                                                             .attributeConverterProviders(
313 
314                                                                 CustomAttributeForDocumentConverterProvider.create(),
315                                                                 defaultProvider())
316                                                             .putString("direct_attr", "sample_value")
317                                                             .put("customObject", customObject,
318                                                                  EnhancedType.of(CustomClassForDocumentAPI.class))
319                                                             .build();
320 
321         assertThat(afterCustomClass.toJson()).isEqualTo("{\"direct_attr\":\"sample_value\",\"customObject\":{\"longNumber\":26,"
322                                                         + "\"string\":\"str_one\"}}");
323 
324         EnhancedDocument enhancedDocument = EnhancedDocument.builder()
325                                                             .putString("direct_attr", "sample_value")
326                                                             .put("customObject", customObject,
327                                                                  EnhancedType.of(CustomClassForDocumentAPI.class)).attributeConverterProviders
328                                                                 (defaultProvider(),
329                                                                  CustomAttributeForDocumentConverterProvider.create())
330                                                             .build();
331 
332         assertThatIllegalStateException().isThrownBy(
333             () -> enhancedDocument.toJson()
334         ).withMessage("Converter not found for "
335                       + "EnhancedType(software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomClassForDocumentAPI)");
336     }
337 
338     @ParameterizedTest
339     @ValueSource(strings = {"", " ", "\t", "  ", "\n", "\r", "\f"})
invalidKeyNames(String escapingString)340     void invalidKeyNames(String escapingString) {
341         assertThatIllegalArgumentException().isThrownBy(() ->
342                                                             EnhancedDocument.builder()
343                                                                             .attributeConverterProviders(defaultProvider())
344                                                                             .putString(escapingString, "sample")
345                                                                             .build())
346                                             .withMessageContaining("Attribute name must not be null or empty.");
347     }
348 
349     @ParameterizedTest
350     @MethodSource("escapeDocumentStrings")
escapingTheValues(String escapingString, String expectedJson)351     void escapingTheValues(String escapingString, String expectedJson) throws JsonProcessingException {
352 
353         EnhancedDocument document = EnhancedDocument.builder()
354                                                     .attributeConverterProviders(defaultProvider())
355                                                     .putString("key", escapingString)
356                                                     .build();
357         assertThat(document.toJson()).isEqualTo(expectedJson);
358         assertThat(new ObjectMapper().readTree(document.toJson())).isNotNull();
359     }
360 
361     @ParameterizedTest
362     @MethodSource("unEscapeDocumentStrings")
unEscapingTheValues(String escapingString, String expectedJson)363     void unEscapingTheValues(String escapingString, String expectedJson) throws JsonProcessingException {
364 
365         EnhancedDocument document = EnhancedDocument.builder()
366                                                     .attributeConverterProviders(defaultProvider())
367                                                     .putString("key", escapingString)
368                                                     .build();
369         assertThat(document.toJson()).isEqualTo(expectedJson);
370         assertThat(new ObjectMapper().readTree(document.toJson())).isNotNull();
371 
372     }
373 
374     @Test
removeParameterFromDocument()375     void removeParameterFromDocument() {
376         EnhancedDocument allSimpleTypes = testDataInstance().dataForScenario("allSimpleTypes").getEnhancedDocument();
377         assertThat(allSimpleTypes.isPresent("nullKey")).isTrue();
378         assertThat(allSimpleTypes.isNull("nullKey")).isTrue();
379         assertThat(allSimpleTypes.getNumber("numberKey").intValue()).isEqualTo(10);
380         assertThat(allSimpleTypes.getString("stringKey")).isEqualTo("stringValue");
381 
382         EnhancedDocument removedAttributesDoc = allSimpleTypes.toBuilder()
383                                                               .remove("nullKey")
384                                                               .remove("numberKey")
385                                                               .build();
386 
387         assertThat(removedAttributesDoc.isPresent("nullKey")).isFalse();
388         assertThat(removedAttributesDoc.isNull("nullKey")).isFalse();
389         assertThat(removedAttributesDoc.isPresent("numberKey")).isFalse();
390         assertThat(removedAttributesDoc.getString("stringKey")).isEqualTo("stringValue");
391 
392         assertThatIllegalArgumentException().isThrownBy(
393                                                 () -> removedAttributesDoc.toBuilder().remove(""))
394                                             .withMessage("Attribute name must not be null or empty");
395 
396 
397         assertThatIllegalArgumentException().isThrownBy(
398                                                 () -> removedAttributesDoc.toBuilder().remove(null))
399                                             .withMessage("Attribute name must not be null or empty");
400     }
401 
402     @Test
nullValueInsertion()403     void nullValueInsertion() {
404 
405         final String SAMPLE_KEY = "sampleKey";
406 
407         String expectedNullMessage = "Value for sampleKey must not be null. Use putNull API to insert a Null value";
408 
409         EnhancedDocument.Builder builder = EnhancedDocument.builder();
410         assertThatNullPointerException().isThrownBy(() -> builder.putString(SAMPLE_KEY, null)).withMessage(expectedNullMessage);
411         assertThatNullPointerException().isThrownBy(() -> builder.put(SAMPLE_KEY, null,
412                                                                       EnhancedType.of(String.class))).withMessageContaining(expectedNullMessage);
413         assertThatNullPointerException().isThrownBy(() -> builder.putNumber(SAMPLE_KEY, null)).withMessage(expectedNullMessage);
414         assertThatNullPointerException().isThrownBy(() -> builder.putBytes(SAMPLE_KEY, null)).withMessage(expectedNullMessage);
415         assertThatNullPointerException().isThrownBy(() -> builder.putStringSet(SAMPLE_KEY, null)).withMessage(expectedNullMessage);
416         assertThatNullPointerException().isThrownBy(() -> builder.putBytesSet(SAMPLE_KEY, null)).withMessage(expectedNullMessage);
417         assertThatNullPointerException().isThrownBy(() -> builder.putJson(SAMPLE_KEY, null)).withMessage(expectedNullMessage);
418         assertThatNullPointerException().isThrownBy(() -> builder.putNumberSet(SAMPLE_KEY, null)).withMessage(expectedNullMessage);
419         assertThatNullPointerException().isThrownBy(() -> builder.putMap(SAMPLE_KEY, null, EnhancedType.of(String.class),
420                                                                          EnhancedType.of(String.class))).withMessage(expectedNullMessage);
421         assertThatNullPointerException().isThrownBy(() -> builder.putList(SAMPLE_KEY, null, EnhancedType.of(String.class))).withMessage(expectedNullMessage);
422     }
423 
424     @Test
accessingNulAttributeValue()425     void accessingNulAttributeValue() {
426         String NULL_KEY = "nullKey";
427         EnhancedDocument enhancedDocument =
428             EnhancedDocument.builder().attributeConverterProviders(defaultProvider()).putNull(NULL_KEY).build();
429 
430         Assertions.assertNull(enhancedDocument.getString(NULL_KEY));
431         Assertions.assertNull(enhancedDocument.getList(NULL_KEY, EnhancedType.of(String.class)));
432         assertThat(enhancedDocument.getBoolean(NULL_KEY)).isNull();
433     }
434 
435     @Test
booleanValueRepresentation()436     void booleanValueRepresentation() {
437         EnhancedDocument.Builder builder = EnhancedDocument.builder()
438                                                            .attributeConverterProviders(defaultProvider());
439         assertThat(builder.putString("boolean", "true").build().getBoolean("boolean")).isTrue();
440         assertThat(builder.putNumber("boolean", 1).build().getBoolean("boolean")).isTrue();
441     }
442 
443     @Test
putAndGetOfCustomTypes_with_EnhancedTypeApi()444     void putAndGetOfCustomTypes_with_EnhancedTypeApi() {
445         CustomClassForDocumentAPI customObject = CustomClassForDocumentAPI.builder().string("str_one")
446                                                                           .longNumber(26L)
447                                                                           .aBoolean(false).build();
448         EnhancedDocument enhancedDocument = EnhancedDocument.builder()
449                                                             .attributeConverterProviders(
450                                                                 CustomAttributeForDocumentConverterProvider.create(),
451                                                                 defaultProvider())
452                                                             .putString("direct_attr", "sample_value")
453                                                             .put("customObject", customObject,
454                                                                  EnhancedType.of(CustomClassForDocumentAPI.class))
455                                                             .build();
456 
457         assertThat(enhancedDocument.get("customObject", EnhancedType.of(CustomClassForDocumentAPI.class)))
458             .isEqualTo(customObject);
459     }
460 
461     @Test
putAndGetOfCustomTypes_with_ClassTypes()462     void putAndGetOfCustomTypes_with_ClassTypes() {
463         CustomClassForDocumentAPI customObject = CustomClassForDocumentAPI.builder().string("str_one")
464                                                                           .longNumber(26L)
465                                                                           .aBoolean(false).build();
466         EnhancedDocument enhancedDocument = EnhancedDocument.builder()
467                                                             .attributeConverterProviders(
468                                                                 CustomAttributeForDocumentConverterProvider.create(),
469                                                                 defaultProvider())
470                                                             .putString("direct_attr", "sample_value")
471                                                             .put("customObject", customObject,
472                                                                  CustomClassForDocumentAPI.class)
473                                                             .build();
474 
475         assertThat(enhancedDocument.get("customObject", CustomClassForDocumentAPI.class)).isEqualTo(customObject);
476     }
477 
478     @Test
error_when_usingClassGetPut_for_CollectionValues()479     void error_when_usingClassGetPut_for_CollectionValues(){
480 
481         assertThatIllegalArgumentException().isThrownBy(
482             () -> EnhancedDocument.builder().put("mapKey", new HashMap(), Map.class))
483                                             .withMessage("Values of type Map are not supported by this API, please use the putMap API instead");
484 
485         assertThatIllegalArgumentException().isThrownBy(
486             () -> EnhancedDocument.builder().put("listKey", new ArrayList<>() , List.class))
487                                             .withMessage("Values of type List are not supported by this API, please use the putList API instead");
488 
489 
490         assertThatIllegalArgumentException().isThrownBy(
491                                                 () -> EnhancedDocument.builder().build().get("mapKey", Map.class))
492                                             .withMessage("Values of type Map are not supported by this API, please use the getMap API instead");
493 
494         assertThatIllegalArgumentException().isThrownBy(
495                                                 () -> EnhancedDocument.builder().build().get("listKey" , List.class))
496                                             .withMessage("Values of type List are not supported by this API, please use the getList API instead");
497     }
498 }
499