Skip to content

Commit f9b41a5

Browse files
committed
Add failing tests highlighting the bug
1 parent ef83581 commit f9b41a5

File tree

3 files changed

+178
-1
lines changed

3 files changed

+178
-1
lines changed

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121

2222
import java.util.Arrays;
2323
import java.util.Collections;
24+
import java.util.Map;
2425

2526
import org.bson.Document;
2627
import org.bson.types.ObjectId;
2728
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.api.DisplayName;
2830
import org.junit.jupiter.api.Test;
2931
import org.junit.jupiter.api.extension.ExtendWith;
3032
import org.mockito.ArgumentCaptor;
@@ -35,6 +37,8 @@
3537
import org.mockito.quality.Strictness;
3638

3739
import org.springframework.dao.InvalidDataAccessApiUsageException;
40+
import org.springframework.data.mapping.context.MappingContext;
41+
import org.springframework.data.mapping.model.SpELContext;
3842
import org.springframework.data.mongodb.MongoDatabaseFactory;
3943
import org.springframework.data.mongodb.core.DocumentTestUtils;
4044
import org.springframework.data.mongodb.core.MongoExceptionTranslator;
@@ -43,6 +47,9 @@
4347
import com.mongodb.client.FindIterable;
4448
import com.mongodb.client.MongoCollection;
4549
import com.mongodb.client.MongoDatabase;
50+
import org.springframework.data.mongodb.core.mapping.DocumentReference;
51+
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
52+
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
4653

4754
/**
4855
* Unit tests for {@link DefaultDbRefResolver}.
@@ -58,6 +65,8 @@ class DefaultDbRefResolverUnitTests {
5865
@Mock MongoDatabase dbMock;
5966
@Mock MongoCollection<Document> collectionMock;
6067
@Mock FindIterable<Document> cursorMock;
68+
@Mock MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
69+
@Mock SpELContext spELContext;
6170
private DefaultDbRefResolver resolver;
6271

6372
@BeforeEach
@@ -91,7 +100,7 @@ void bulkFetchShouldLoadDbRefsCorrectly() {
91100
}
92101

93102
@Test // DATAMONGO-1194
94-
void bulkFetchShouldThrowExceptionWhenUsingDifferntCollectionsWithinSetOfReferences() {
103+
void bulkFetchShouldThrowExceptionWhenUsingDifferentCollectionsWithinSetOfReferences() {
95104

96105
DBRef ref1 = new DBRef("collection-1", new ObjectId());
97106
DBRef ref2 = new DBRef("collection-2", new ObjectId());
@@ -134,4 +143,75 @@ void bulkFetchContainsDuplicates() {
134143

135144
assertThat(resolver.bulkFetch(Arrays.asList(ref1, ref2))).containsExactly(document, document);
136145
}
146+
147+
@Test // GH-5065
148+
@DisplayName("GH-5065: Empty Map with @DocumentReference annotation should deserialize to an empty map.")
149+
void resolveEmptyMapIsNotNull() {
150+
DocumentReference documentReference = mock(DocumentReference.class);
151+
when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }");
152+
when(documentReference.sort()).thenReturn("");
153+
when(documentReference.lazy()).thenReturn(false);
154+
MongoPersistentProperty property = mock(MongoPersistentProperty.class);
155+
when(property.isCollectionLike()).thenReturn(false);
156+
when(property.isMap()).thenReturn(true);
157+
when(property.isDocumentReference()).thenReturn(true);
158+
when(property.getDocumentReference()).thenReturn(documentReference);
159+
DocumentReferenceSource source = mock(DocumentReferenceSource.class);
160+
when(source.getTargetSource()).thenReturn(Document.parse("{}"));
161+
ReferenceLookupDelegate lookupDelegate = new ReferenceLookupDelegate(mappingContext, spELContext);
162+
163+
ReferenceResolver.MongoEntityReader entityReader = mock(ReferenceResolver.MongoEntityReader.class);
164+
165+
Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader);
166+
167+
verify(property, times(3)).isMap();
168+
verify(property, times(2)).isDocumentReference();
169+
verify(property, times(2)).getDocumentReference();
170+
verify(property, times(3)).isCollectionLike();
171+
verify(documentReference, times(1)).lookup();
172+
verify(documentReference, times(1)).sort();
173+
verify(documentReference, times(1)).lazy();
174+
verify(source, times(3)).getTargetSource();
175+
verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked.
176+
assertThat(target)
177+
.isNotNull()
178+
.isInstanceOf(Map.class);
179+
}
180+
181+
@Test // GH-5065
182+
@DisplayName("GH-5065: Lazy loaded empty Map with @DocumentReference annotation should deserialize to an empty map with a non-null values property.")
183+
void resolveLazyLoadedEmptyMapIsNotNull() {
184+
DocumentReference documentReference = mock(DocumentReference.class);
185+
when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }");
186+
when(documentReference.sort()).thenReturn("");
187+
when(documentReference.lazy()).thenReturn(true);
188+
MongoPersistentProperty property = mock(MongoPersistentProperty.class);
189+
when(property.isCollectionLike()).thenReturn(false);
190+
when(property.isMap()).thenReturn(true);
191+
when(property.isDocumentReference()).thenReturn(true);
192+
when(property.getDocumentReference()).thenReturn(documentReference);
193+
//noinspection rawtypes,unchecked
194+
when(property.getType()).thenReturn((Class) Map.class);
195+
DocumentReferenceSource source = mock(DocumentReferenceSource.class);
196+
when(source.getTargetSource()).thenReturn(Document.parse("{}"));
197+
ReferenceLookupDelegate lookupDelegate = new ReferenceLookupDelegate(mappingContext, spELContext);
198+
199+
ReferenceResolver.MongoEntityReader entityReader = mock(ReferenceResolver.MongoEntityReader.class);
200+
201+
Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader);
202+
203+
verify(property, times(1)).isMap();
204+
verify(property, times(1)).isDocumentReference();
205+
verify(property, times(1)).getDocumentReference();
206+
verify(property, times(1)).isCollectionLike();
207+
verify(property, times(1)).getType();
208+
verify(documentReference, times(1)).lazy();
209+
verify(source, times(1)).getTargetSource();
210+
verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked.
211+
212+
assertThat(target)
213+
.isNotNull()
214+
.isInstanceOf(Map.class)
215+
.asInstanceOf(MAP).values().isNotNull();
216+
}
137217
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@
3434

3535
import org.bson.Document;
3636
import org.junit.jupiter.api.BeforeEach;
37+
import org.junit.jupiter.api.DisplayName;
38+
import org.junit.jupiter.api.Nested;
3739
import org.junit.jupiter.api.Test;
3840

3941
import org.springframework.data.annotation.Id;
4042
import org.springframework.data.mongodb.MongoDatabaseFactory;
4143
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
4244
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
4345
import org.springframework.data.mongodb.core.mapping.DBRef;
46+
import org.springframework.data.mongodb.core.mapping.DocumentReference;
4447
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
4548
import org.springframework.data.mongodb.test.util.Client;
4649

@@ -478,4 +481,64 @@ public String toString() {
478481
+ ", localTime=" + this.getLocalTime() + ", localDateTime=" + this.getLocalDateTime() + ")";
479482
}
480483
}
484+
485+
@Nested // GH-5065
486+
@DisplayName("GH-5065: Empty Map Tests")
487+
class EmptyMapTests {
488+
489+
@Test // GH-5065
490+
@DisplayName("Passing test to indicate that the problem is not a more generic issue with maps in general.")
491+
void readsEmptyMapCorrectly() {
492+
org.bson.Document document = org.bson.Document.parse("{\"map\":{}}");
493+
EmptyMapDocument target = converter.read(EmptyMapDocument.class, document);
494+
assertThat(target.map).isNotNull().isEmpty();
495+
}
496+
497+
@Test // GH-5065
498+
@DisplayName("Converter should read an empty object as an empty map when using @DocumentReference.")
499+
void readsEmptyMapWithDocumentReferenceCorrectly() {
500+
org.bson.Document document = org.bson.Document.parse("{\"map\":{}}");
501+
DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document);
502+
assertThat(target.map).isNotNull().isEmpty();
503+
}
504+
505+
@Test // GH-5065
506+
@DisplayName("Converter should read an empty object as an empty map with a valis values property when using @DocumentReference(lazy = true).")
507+
void readsEmptyMapWithLazyLoadedDocumentReferenceCorrectly() {
508+
org.bson.Document document = org.bson.Document.parse("{\"map\":{}}");
509+
LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document);
510+
assertThat(target.map).isNotNull();
511+
assertThat(target.map.values()).isNotNull();
512+
}
513+
514+
static class EmptyMapDocument {
515+
516+
Map<String, String> map;
517+
518+
public EmptyMapDocument(Map<String, String> map) {
519+
this.map = map;
520+
}
521+
}
522+
523+
static class DocumentReferenceEmptyMapDocument {
524+
525+
@DocumentReference
526+
Map<String, String> map;
527+
528+
public DocumentReferenceEmptyMapDocument(Map<String, String> map) {
529+
this.map = map;
530+
}
531+
}
532+
533+
static class LazyDocumentReferenceEmptyMapDocument {
534+
535+
@DocumentReference(lazy = true)
536+
Map<String, String> map;
537+
538+
public LazyDocumentReferenceEmptyMapDocument(Map<String, String> map) {
539+
this.map = map;
540+
}
541+
}
542+
}
543+
481544
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
import static org.mockito.Mockito.*;
2020

2121
import java.util.Collections;
22+
import java.util.Map;
2223

24+
import org.bson.Document;
2325
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.DisplayName;
2427
import org.junit.jupiter.api.Test;
2528
import org.junit.jupiter.api.extension.ExtendWith;
2629
import org.mockito.Mock;
@@ -94,4 +97,35 @@ void shouldResolveEmptyMapOnEmptyTargetCollection() {
9497
lookupDelegate.readReference(property, Collections.emptyMap(), lookupFunction, entityReader);
9598
verify(lookupFunction, never()).apply(any(), any());
9699
}
100+
101+
@Test // GH-5065
102+
@DisplayName("GH-5065: Empty Map with @DocumentReference annotation should deserialize to an empty map.")
103+
void shouldResolveEmptyMapOnEmptyDocumentReferenceMapProperty() {
104+
DocumentReference documentReference = mock(DocumentReference.class);
105+
when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }");
106+
when(documentReference.sort()).thenReturn("");
107+
MongoPersistentProperty property = mock(MongoPersistentProperty.class);
108+
when(property.isMap()).thenReturn(true);
109+
when(property.isDocumentReference()).thenReturn(true);
110+
when(property.getDocumentReference()).thenReturn(documentReference);
111+
when(property.isCollectionLike()).thenReturn(false);
112+
DocumentReferenceSource source = mock(DocumentReferenceSource.class);
113+
when(source.getTargetSource()).thenReturn(Document.parse("{}"));
114+
ReferenceLookupDelegate.LookupFunction lookupFunction = mock(ReferenceLookupDelegate.LookupFunction.class);
115+
116+
Object target = lookupDelegate.readReference(property, source, lookupFunction, entityReader);
117+
118+
verify(lookupFunction, never()).apply(any(), any()); // Since we mocked it, make sure it is never called.
119+
verify(property, times(2)).isMap();
120+
verify(property, times(1)).isDocumentReference();
121+
verify(property, times(1)).getDocumentReference();
122+
verify(property, times(2)).isCollectionLike();
123+
verify(documentReference, times(1)).lookup();
124+
verify(documentReference, times(1)).sort();
125+
verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked.
126+
assertThat(target)
127+
.isNotNull()
128+
.isInstanceOf(Map.class);
129+
}
130+
97131
}

0 commit comments

Comments
 (0)