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

http://github.com/SpringSource/spring-data-mongodb · Java · 2580 lines · 1765 code · 772 blank · 43 comment · 2 complexity · 65e9bbd10a9708aea10abf8a9536ab9e MD5 · raw file

Large files are truncated click here to view the full file

  1. /*
  2. * Copyright 2010-2021 the original author or authors.
  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. * You may obtain a copy of the License at
  7. *
  8. * https://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.data.mongodb.core;
  17. import static org.mockito.Mockito.*;
  18. import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
  19. import static org.springframework.data.mongodb.test.util.Assertions.*;
  20. import lombok.AllArgsConstructor;
  21. import lombok.Data;
  22. import lombok.NoArgsConstructor;
  23. import java.math.BigInteger;
  24. import java.time.Duration;
  25. import java.time.Instant;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.Collection;
  29. import java.util.Collections;
  30. import java.util.Iterator;
  31. import java.util.LinkedHashMap;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.Optional;
  35. import java.util.concurrent.TimeUnit;
  36. import java.util.regex.Pattern;
  37. import org.assertj.core.api.Assertions;
  38. import org.bson.Document;
  39. import org.bson.conversions.Bson;
  40. import org.bson.types.ObjectId;
  41. import org.junit.jupiter.api.BeforeEach;
  42. import org.junit.jupiter.api.Disabled;
  43. import org.junit.jupiter.api.Test;
  44. import org.mockito.ArgumentCaptor;
  45. import org.mockito.ArgumentMatcher;
  46. import org.mockito.Mock;
  47. import org.mockito.Mockito;
  48. import org.mockito.junit.jupiter.MockitoSettings;
  49. import org.mockito.quality.Strictness;
  50. import org.springframework.beans.factory.annotation.Value;
  51. import org.springframework.context.ApplicationContext;
  52. import org.springframework.context.ApplicationListener;
  53. import org.springframework.context.support.GenericApplicationContext;
  54. import org.springframework.context.support.StaticApplicationContext;
  55. import org.springframework.core.convert.converter.Converter;
  56. import org.springframework.dao.DataAccessException;
  57. import org.springframework.dao.InvalidDataAccessApiUsageException;
  58. import org.springframework.data.annotation.Id;
  59. import org.springframework.data.annotation.Transient;
  60. import org.springframework.data.annotation.Version;
  61. import org.springframework.data.convert.CustomConversions;
  62. import org.springframework.data.domain.Sort;
  63. import org.springframework.data.geo.Point;
  64. import org.springframework.data.mapping.MappingException;
  65. import org.springframework.data.mapping.callback.EntityCallbacks;
  66. import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
  67. import org.springframework.data.mapping.context.MappingContext;
  68. import org.springframework.data.mongodb.MongoDatabaseFactory;
  69. import org.springframework.data.mongodb.core.aggregation.*;
  70. import org.springframework.data.mongodb.core.aggregation.ComparisonOperators.Gte;
  71. import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator;
  72. import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
  73. import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
  74. import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
  75. import org.springframework.data.mongodb.core.convert.QueryMapper;
  76. import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
  77. import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
  78. import org.springframework.data.mongodb.core.mapping.Field;
  79. import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
  80. import org.springframework.data.mongodb.core.mapping.Sharded;
  81. import org.springframework.data.mongodb.core.mapping.TimeSeries;
  82. import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
  83. import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
  84. import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback;
  85. import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
  86. import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback;
  87. import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
  88. import org.springframework.data.mongodb.core.mapping.event.BeforeSaveCallback;
  89. import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
  90. import org.springframework.data.mongodb.core.mapreduce.GroupBy;
  91. import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
  92. import org.springframework.data.mongodb.core.query.BasicQuery;
  93. import org.springframework.data.mongodb.core.query.Collation;
  94. import org.springframework.data.mongodb.core.query.Criteria;
  95. import org.springframework.data.mongodb.core.query.NearQuery;
  96. import org.springframework.data.mongodb.core.query.Query;
  97. import org.springframework.data.mongodb.core.query.Update;
  98. import org.springframework.data.mongodb.core.timeseries.Granularity;
  99. import org.springframework.data.mongodb.util.BsonUtils;
  100. import org.springframework.lang.Nullable;
  101. import org.springframework.test.util.ReflectionTestUtils;
  102. import org.springframework.util.CollectionUtils;
  103. import com.mongodb.MongoClientSettings;
  104. import com.mongodb.MongoException;
  105. import com.mongodb.MongoNamespace;
  106. import com.mongodb.ReadPreference;
  107. import com.mongodb.ServerAddress;
  108. import com.mongodb.ServerCursor;
  109. import com.mongodb.WriteConcern;
  110. import com.mongodb.client.AggregateIterable;
  111. import com.mongodb.client.DistinctIterable;
  112. import com.mongodb.client.FindIterable;
  113. import com.mongodb.client.MapReduceIterable;
  114. import com.mongodb.client.MongoClient;
  115. import com.mongodb.client.MongoCollection;
  116. import com.mongodb.client.MongoCursor;
  117. import com.mongodb.client.MongoDatabase;
  118. import com.mongodb.client.model.CountOptions;
  119. import com.mongodb.client.model.CreateCollectionOptions;
  120. import com.mongodb.client.model.DeleteOptions;
  121. import com.mongodb.client.model.FindOneAndDeleteOptions;
  122. import com.mongodb.client.model.FindOneAndReplaceOptions;
  123. import com.mongodb.client.model.FindOneAndUpdateOptions;
  124. import com.mongodb.client.model.MapReduceAction;
  125. import com.mongodb.client.model.ReplaceOptions;
  126. import com.mongodb.client.model.TimeSeriesGranularity;
  127. import com.mongodb.client.model.UpdateOptions;
  128. import com.mongodb.client.result.DeleteResult;
  129. import com.mongodb.client.result.UpdateResult;
  130. /**
  131. * Unit tests for {@link MongoTemplate}.
  132. *
  133. * @author Oliver Gierke
  134. * @author Christoph Strobl
  135. * @author Mark Paluch
  136. * @author Michael J. Simons
  137. * @author Roman Puchkovskiy
  138. * @author Yadhukrishna S Pai
  139. */
  140. @MockitoSettings(strictness = Strictness.LENIENT)
  141. public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
  142. private MongoTemplate template;
  143. @Mock MongoDatabaseFactory factory;
  144. @Mock MongoClient mongo;
  145. @Mock MongoDatabase db;
  146. @Mock MongoCollection<Document> collection;
  147. @Mock MongoCollection<Document> collectionWithWriteConcern;
  148. @Mock MongoCursor<Document> cursor;
  149. @Mock FindIterable<Document> findIterable;
  150. @Mock AggregateIterable aggregateIterable;
  151. @Mock MapReduceIterable mapReduceIterable;
  152. @Mock DistinctIterable distinctIterable;
  153. @Mock UpdateResult updateResult;
  154. @Mock DeleteResult deleteResult;
  155. private Document commandResultDocument = new Document();
  156. private MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
  157. private MappingMongoConverter converter;
  158. private MongoMappingContext mappingContext;
  159. @BeforeEach
  160. void beforeEach() {
  161. when(findIterable.iterator()).thenReturn(cursor);
  162. when(factory.getMongoDatabase()).thenReturn(db);
  163. when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
  164. when(factory.getCodecRegistry()).thenReturn(MongoClientSettings.getDefaultCodecRegistry());
  165. when(db.getCollection(any(String.class), eq(Document.class))).thenReturn(collection);
  166. when(db.runCommand(any(), any(Class.class))).thenReturn(commandResultDocument);
  167. when(collection.find(any(org.bson.Document.class), any(Class.class))).thenReturn(findIterable);
  168. when(collection.mapReduce(any(), any(), eq(Document.class))).thenReturn(mapReduceIterable);
  169. when(collection.countDocuments(any(Bson.class), any(CountOptions.class))).thenReturn(1L);
  170. when(collection.estimatedDocumentCount(any())).thenReturn(1L);
  171. when(collection.getNamespace()).thenReturn(new MongoNamespace("db.mock-collection"));
  172. when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
  173. when(collection.withReadPreference(any())).thenReturn(collection);
  174. when(collection.replaceOne(any(), any(), any(ReplaceOptions.class))).thenReturn(updateResult);
  175. when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
  176. when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctIterable);
  177. when(collectionWithWriteConcern.deleteOne(any(Bson.class), any())).thenReturn(deleteResult);
  178. when(findIterable.projection(any())).thenReturn(findIterable);
  179. when(findIterable.sort(any(org.bson.Document.class))).thenReturn(findIterable);
  180. when(findIterable.collation(any())).thenReturn(findIterable);
  181. when(findIterable.limit(anyInt())).thenReturn(findIterable);
  182. when(mapReduceIterable.collation(any())).thenReturn(mapReduceIterable);
  183. when(mapReduceIterable.sort(any())).thenReturn(mapReduceIterable);
  184. when(mapReduceIterable.iterator()).thenReturn(cursor);
  185. when(mapReduceIterable.filter(any())).thenReturn(mapReduceIterable);
  186. when(mapReduceIterable.collectionName(any())).thenReturn(mapReduceIterable);
  187. when(mapReduceIterable.databaseName(any())).thenReturn(mapReduceIterable);
  188. when(mapReduceIterable.action(any())).thenReturn(mapReduceIterable);
  189. when(aggregateIterable.collation(any())).thenReturn(aggregateIterable);
  190. when(aggregateIterable.allowDiskUse(any())).thenReturn(aggregateIterable);
  191. when(aggregateIterable.batchSize(anyInt())).thenReturn(aggregateIterable);
  192. when(aggregateIterable.map(any())).thenReturn(aggregateIterable);
  193. when(aggregateIterable.maxTime(anyLong(), any())).thenReturn(aggregateIterable);
  194. when(aggregateIterable.into(any())).thenReturn(Collections.emptyList());
  195. when(distinctIterable.collation(any())).thenReturn(distinctIterable);
  196. when(distinctIterable.map(any())).thenReturn(distinctIterable);
  197. when(distinctIterable.into(any())).thenReturn(Collections.emptyList());
  198. this.mappingContext = new MongoMappingContext();
  199. mappingContext.setAutoIndexCreation(true);
  200. mappingContext.setSimpleTypeHolder(new MongoCustomConversions(Collections.emptyList()).getSimpleTypeHolder());
  201. mappingContext.afterPropertiesSet();
  202. this.converter = spy(new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext));
  203. converter.afterPropertiesSet();
  204. this.template = new MongoTemplate(factory, converter);
  205. }
  206. @Test
  207. void rejectsNullDatabaseName() {
  208. assertThatIllegalArgumentException().isThrownBy(() -> new MongoTemplate(mongo, null));
  209. }
  210. @Test // DATAMONGO-1968
  211. void rejectsNullMongo() {
  212. assertThatIllegalArgumentException().isThrownBy(() -> new MongoTemplate((MongoClient) null, "database"));
  213. }
  214. @Test // DATAMONGO-1968
  215. void rejectsNullMongoClient() {
  216. assertThatIllegalArgumentException()
  217. .isThrownBy(() -> new MongoTemplate((com.mongodb.client.MongoClient) null, "database"));
  218. }
  219. @Test // DATAMONGO-1870
  220. void removeHandlesMongoExceptionProperly() {
  221. MongoTemplate template = mockOutGetDb();
  222. assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> template.remove(null, "collection"));
  223. }
  224. @Test
  225. void defaultsConverterToMappingMongoConverter() {
  226. MongoTemplate template = new MongoTemplate(mongo, "database");
  227. assertThat(ReflectionTestUtils.getField(template, "mongoConverter") instanceof MappingMongoConverter).isTrue();
  228. }
  229. @Test
  230. void rejectsNotFoundMapReduceResource() {
  231. GenericApplicationContext ctx = new GenericApplicationContext();
  232. ctx.refresh();
  233. template.setApplicationContext(ctx);
  234. assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
  235. .isThrownBy(() -> template.mapReduce("foo", "classpath:doesNotExist.js", "function() {}", Person.class));
  236. }
  237. @Test // DATAMONGO-322
  238. void rejectsEntityWithNullIdIfNotSupportedIdType() {
  239. Object entity = new NotAutogenerateableId();
  240. assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() -> template.save(entity));
  241. }
  242. @Test // DATAMONGO-322
  243. void storesEntityWithSetIdAlthoughNotAutogenerateable() {
  244. NotAutogenerateableId entity = new NotAutogenerateableId();
  245. entity.id = 1;
  246. template.save(entity);
  247. }
  248. @Test // DATAMONGO-322
  249. void autogeneratesIdForEntityWithAutogeneratableId() {
  250. this.converter.afterPropertiesSet();
  251. MongoTemplate template = spy(this.template);
  252. doReturn(new ObjectId()).when(template).saveDocument(any(String.class), any(Document.class), any(Class.class));
  253. AutogenerateableId entity = new AutogenerateableId();
  254. template.save(entity);
  255. assertThat(entity.id).isNotNull();
  256. }
  257. @Test // DATAMONGO-1912
  258. void autogeneratesIdForMap() {
  259. MongoTemplate template = spy(this.template);
  260. doReturn(new ObjectId()).when(template).saveDocument(any(String.class), any(Document.class), any(Class.class));
  261. Map<String, String> entity = new LinkedHashMap<>();
  262. template.save(entity, "foo");
  263. assertThat(entity).containsKey("_id");
  264. }
  265. @Test // DATAMONGO-374
  266. void convertsUpdateConstraintsUsingConverters() {
  267. CustomConversions conversions = new MongoCustomConversions(Collections.singletonList(MyConverter.INSTANCE));
  268. this.converter.setCustomConversions(conversions);
  269. this.converter.afterPropertiesSet();
  270. Query query = new Query();
  271. Update update = new Update().set("foo", new AutogenerateableId());
  272. template.updateFirst(query, update, Wrapper.class);
  273. QueryMapper queryMapper = new QueryMapper(converter);
  274. Document reference = queryMapper.getMappedObject(update.getUpdateObject(), Optional.empty());
  275. verify(collection, times(1)).updateOne(any(org.bson.Document.class), eq(reference), any(UpdateOptions.class));
  276. }
  277. @Test // DATAMONGO-474
  278. void setsUnpopulatedIdField() {
  279. NotAutogenerateableId entity = new NotAutogenerateableId();
  280. template.populateIdIfNecessary(entity, 5);
  281. assertThat(entity.id).isEqualTo(5);
  282. }
  283. @Test // DATAMONGO-474
  284. void doesNotSetAlreadyPopulatedId() {
  285. NotAutogenerateableId entity = new NotAutogenerateableId();
  286. entity.id = 5;
  287. template.populateIdIfNecessary(entity, 7);
  288. assertThat(entity.id).isEqualTo(5);
  289. }
  290. @Test // DATAMONGO-868
  291. void findAndModifyShouldBumpVersionByOneWhenVersionFieldNotIncludedInUpdate() {
  292. VersionedEntity v = new VersionedEntity();
  293. v.id = 1;
  294. v.version = 0;
  295. ArgumentCaptor<org.bson.Document> captor = ArgumentCaptor.forClass(org.bson.Document.class);
  296. template.findAndModify(new Query(), new Update().set("id", "10"), VersionedEntity.class);
  297. verify(collection, times(1)).findOneAndUpdate(any(org.bson.Document.class), captor.capture(),
  298. any(FindOneAndUpdateOptions.class));
  299. assertThat(captor.getValue().get("$inc")).isEqualTo(new Document("version", 1L));
  300. }
  301. @Test // DATAMONGO-868
  302. void findAndModifyShouldNotBumpVersionByOneWhenVersionFieldAlreadyIncludedInUpdate() {
  303. VersionedEntity v = new VersionedEntity();
  304. v.id = 1;
  305. v.version = 0;
  306. ArgumentCaptor<org.bson.Document> captor = ArgumentCaptor.forClass(org.bson.Document.class);
  307. template.findAndModify(new Query(), new Update().set("version", 100), VersionedEntity.class);
  308. verify(collection, times(1)).findOneAndUpdate(any(org.bson.Document.class), captor.capture(),
  309. any(FindOneAndUpdateOptions.class));
  310. assertThat(captor.getValue().get("$set")).isEqualTo(new Document("version", 100));
  311. assertThat(captor.getValue().get("$inc")).isNull();
  312. }
  313. @Test // DATAMONGO-533
  314. void registersDefaultEntityIndexCreatorIfApplicationContextHasOneForDifferentMappingContext() {
  315. GenericApplicationContext applicationContext = new GenericApplicationContext();
  316. applicationContext.getBeanFactory().registerSingleton("foo",
  317. new MongoPersistentEntityIndexCreator(new MongoMappingContext(), template));
  318. applicationContext.refresh();
  319. GenericApplicationContext spy = spy(applicationContext);
  320. MongoTemplate mongoTemplate = new MongoTemplate(factory, converter);
  321. mongoTemplate.setApplicationContext(spy);
  322. verify(spy, times(1)).addApplicationListener(argThat(new ArgumentMatcher<MongoPersistentEntityIndexCreator>() {
  323. @Override
  324. public boolean matches(MongoPersistentEntityIndexCreator argument) {
  325. return argument.isIndexCreatorFor(mappingContext);
  326. }
  327. }));
  328. }
  329. @Test // DATAMONGO-566
  330. void findAllAndRemoveShouldRetrieveMatchingDocumentsPriorToRemoval() {
  331. BasicQuery query = new BasicQuery("{'foo':'bar'}");
  332. template.findAllAndRemove(query, VersionedEntity.class);
  333. verify(collection, times(1)).find(Mockito.eq(query.getQueryObject()), any(Class.class));
  334. }
  335. @Test // GH-3648
  336. void shouldThrowExceptionIfEntityReaderReturnsNull() {
  337. when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
  338. when(cursor.next()).thenReturn(new org.bson.Document("_id", Integer.valueOf(0)));
  339. MappingMongoConverter converter = mock(MappingMongoConverter.class);
  340. when(converter.getMappingContext()).thenReturn((MappingContext) mappingContext);
  341. template = new MongoTemplate(factory, converter);
  342. assertThatExceptionOfType(MappingException.class).isThrownBy(() -> template.findAll(Person.class))
  343. .withMessageContaining("returned null");
  344. }
  345. @Test // DATAMONGO-566
  346. void findAllAndRemoveShouldRemoveDocumentsReturedByFindQuery() {
  347. when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
  348. when(cursor.next()).thenReturn(new org.bson.Document("_id", Integer.valueOf(0)))
  349. .thenReturn(new org.bson.Document("_id", Integer.valueOf(1)));
  350. ArgumentCaptor<org.bson.Document> queryCaptor = ArgumentCaptor.forClass(org.bson.Document.class);
  351. BasicQuery query = new BasicQuery("{'foo':'bar'}");
  352. template.findAllAndRemove(query, VersionedEntity.class);
  353. verify(collection, times(1)).deleteMany(queryCaptor.capture(), any());
  354. Document idField = DocumentTestUtils.getAsDocument(queryCaptor.getValue(), "_id");
  355. assertThat((List<Object>) idField.get("$in")).containsExactly(Integer.valueOf(0), Integer.valueOf(1));
  356. }
  357. @Test // DATAMONGO-566
  358. void findAllAndRemoveShouldNotTriggerRemoveIfFindResultIsEmpty() {
  359. template.findAllAndRemove(new BasicQuery("{'foo':'bar'}"), VersionedEntity.class);
  360. verify(collection, never()).deleteMany(any(org.bson.Document.class));
  361. }
  362. @Test // DATAMONGO-948
  363. void sortShouldBeTakenAsIsWhenExecutingQueryWithoutSpecificTypeInformation() {
  364. Query query = Query.query(Criteria.where("foo").is("bar")).with(Sort.by("foo"));
  365. template.executeQuery(query, "collection1", new DocumentCallbackHandler() {
  366. @Override
  367. public void processDocument(Document document) throws MongoException, DataAccessException {
  368. // nothing to do - just a test
  369. }
  370. });
  371. ArgumentCaptor<org.bson.Document> captor = ArgumentCaptor.forClass(org.bson.Document.class);
  372. verify(findIterable, times(1)).sort(captor.capture());
  373. assertThat(captor.getValue()).isEqualTo(new Document("foo", 1));
  374. }
  375. @Test // DATAMONGO-1166, DATAMONGO-1824
  376. void aggregateShouldHonorReadPreferenceWhenSet() {
  377. template.setReadPreference(ReadPreference.secondary());
  378. template.aggregate(newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class);
  379. verify(collection).withReadPreference(eq(ReadPreference.secondary()));
  380. }
  381. @Test // DATAMONGO-1166, DATAMONGO-1824
  382. void aggregateShouldIgnoreReadPreferenceWhenNotSet() {
  383. template.aggregate(newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class);
  384. verify(collection, never()).withReadPreference(any());
  385. }
  386. @Test // DATAMONGO-2153
  387. void aggregateShouldHonorOptionsComment() {
  388. AggregationOptions options = AggregationOptions.builder().comment("expensive").build();
  389. template.aggregate(newAggregation(Aggregation.unwind("foo")).withOptions(options), "collection-1", Wrapper.class);
  390. verify(aggregateIterable).comment("expensive");
  391. }
  392. @Test // DATAMONGO-1836
  393. void aggregateShouldHonorOptionsHint() {
  394. Document hint = new Document("dummyField", 1);
  395. AggregationOptions options = AggregationOptions.builder().hint(hint).build();
  396. template.aggregate(newAggregation(Aggregation.unwind("foo")).withOptions(options), "collection-1", Wrapper.class);
  397. verify(aggregateIterable).hint(hint);
  398. }
  399. @Test // GH-3542
  400. void aggregateShouldUseRelaxedMappingByDefault() {
  401. MongoTemplate template = new MongoTemplate(factory, converter) {
  402. @Override
  403. protected <O> AggregationResults<O> doAggregate(Aggregation aggregation, String collectionName,
  404. Class<O> outputType, AggregationOperationContext context) {
  405. assertThat(context).isInstanceOf(RelaxedTypeBasedAggregationOperationContext.class);
  406. return super.doAggregate(aggregation, collectionName, outputType, context);
  407. }
  408. };
  409. template.aggregate(
  410. newAggregation(Jedi.class, Aggregation.unwind("foo")).withOptions(AggregationOptions.builder().build()),
  411. Jedi.class);
  412. }
  413. @Test // GH-3542
  414. void aggregateShouldUseStrictMappingIfOptionsIndicate() {
  415. MongoTemplate template = new MongoTemplate(factory, converter) {
  416. @Override
  417. protected <O> AggregationResults<O> doAggregate(Aggregation aggregation, String collectionName,
  418. Class<O> outputType, AggregationOperationContext context) {
  419. assertThat(context).isInstanceOf(TypeBasedAggregationOperationContext.class);
  420. return super.doAggregate(aggregation, collectionName, outputType, context);
  421. }
  422. };
  423. assertThatExceptionOfType(InvalidPersistentPropertyPath.class)
  424. .isThrownBy(() -> template.aggregate(newAggregation(Jedi.class, Aggregation.unwind("foo"))
  425. .withOptions(AggregationOptions.builder().strictMapping().build()), Jedi.class));
  426. }
  427. @Test // DATAMONGO-1166, DATAMONGO-2264
  428. void geoNearShouldHonorReadPreferenceWhenSet() {
  429. template.setReadPreference(ReadPreference.secondary());
  430. NearQuery query = NearQuery.near(new Point(1, 1));
  431. template.geoNear(query, Wrapper.class);
  432. verify(collection).withReadPreference(eq(ReadPreference.secondary()));
  433. }
  434. @Test // DATAMONGO-1166, DATAMONGO-2264
  435. void geoNearShouldIgnoreReadPreferenceWhenNotSet() {
  436. NearQuery query = NearQuery.near(new Point(1, 1));
  437. template.geoNear(query, Wrapper.class);
  438. verify(collection, never()).withReadPreference(any());
  439. }
  440. @Test // DATAMONGO-1334
  441. @Disabled("TODO: mongo3 - a bit hard to tests with the immutable object stuff")
  442. void mapReduceShouldUseZeroAsDefaultLimit() {
  443. MongoCursor cursor = mock(MongoCursor.class);
  444. MapReduceIterable output = mock(MapReduceIterable.class);
  445. when(output.limit(anyInt())).thenReturn(output);
  446. when(output.sort(any(Document.class))).thenReturn(output);
  447. when(output.filter(any(Document.class))).thenReturn(output);
  448. when(output.iterator()).thenReturn(cursor);
  449. when(cursor.hasNext()).thenReturn(false);
  450. when(collection.mapReduce(anyString(), anyString())).thenReturn(output);
  451. Query query = new BasicQuery("{'foo':'bar'}");
  452. template.mapReduce(query, "collection", "function(){}", "function(key,values){}", Wrapper.class);
  453. verify(output, times(1)).limit(1);
  454. }
  455. @Test // DATAMONGO-1334
  456. void mapReduceShouldPickUpLimitFromQuery() {
  457. MongoCursor cursor = mock(MongoCursor.class);
  458. MapReduceIterable output = mock(MapReduceIterable.class);
  459. when(output.limit(anyInt())).thenReturn(output);
  460. when(output.sort(any())).thenReturn(output);
  461. when(output.filter(any(Document.class))).thenReturn(output);
  462. when(output.iterator()).thenReturn(cursor);
  463. when(cursor.hasNext()).thenReturn(false);
  464. when(collection.mapReduce(anyString(), anyString(), eq(Document.class))).thenReturn(output);
  465. Query query = new BasicQuery("{'foo':'bar'}");
  466. query.limit(100);
  467. template.mapReduce(query, "collection", "function(){}", "function(key,values){}", Wrapper.class);
  468. verify(output, times(1)).limit(100);
  469. }
  470. @Test // DATAMONGO-1334
  471. void mapReduceShouldPickUpLimitFromOptions() {
  472. MongoCursor cursor = mock(MongoCursor.class);
  473. MapReduceIterable output = mock(MapReduceIterable.class);
  474. when(output.limit(anyInt())).thenReturn(output);
  475. when(output.sort(any())).thenReturn(output);
  476. when(output.filter(any(Document.class))).thenReturn(output);
  477. when(output.iterator()).thenReturn(cursor);
  478. when(cursor.hasNext()).thenReturn(false);
  479. when(collection.mapReduce(anyString(), anyString(), eq(Document.class))).thenReturn(output);
  480. Query query = new BasicQuery("{'foo':'bar'}");
  481. template.mapReduce(query, "collection", "function(){}", "function(key,values){}",
  482. new MapReduceOptions().limit(1000), Wrapper.class);
  483. verify(output, times(1)).limit(1000);
  484. }
  485. @Test // DATAMONGO-1334
  486. void mapReduceShouldPickUpLimitFromOptionsWhenQueryIsNotPresent() {
  487. MongoCursor cursor = mock(MongoCursor.class);
  488. MapReduceIterable output = mock(MapReduceIterable.class);
  489. when(output.limit(anyInt())).thenReturn(output);
  490. when(output.sort(any())).thenReturn(output);
  491. when(output.filter(any())).thenReturn(output);
  492. when(output.iterator()).thenReturn(cursor);
  493. when(cursor.hasNext()).thenReturn(false);
  494. when(collection.mapReduce(anyString(), anyString(), eq(Document.class))).thenReturn(output);
  495. template.mapReduce("collection", "function(){}", "function(key,values){}", new MapReduceOptions().limit(1000),
  496. Wrapper.class);
  497. verify(output, times(1)).limit(1000);
  498. }
  499. @Test // DATAMONGO-1334
  500. void mapReduceShouldPickUpLimitFromOptionsEvenWhenQueryDefinesItDifferently() {
  501. MongoCursor cursor = mock(MongoCursor.class);
  502. MapReduceIterable output = mock(MapReduceIterable.class);
  503. when(output.limit(anyInt())).thenReturn(output);
  504. when(output.sort(any())).thenReturn(output);
  505. when(output.filter(any(Document.class))).thenReturn(output);
  506. when(output.iterator()).thenReturn(cursor);
  507. when(cursor.hasNext()).thenReturn(false);
  508. when(collection.mapReduce(anyString(), anyString(), eq(Document.class))).thenReturn(output);
  509. Query query = new BasicQuery("{'foo':'bar'}");
  510. query.limit(100);
  511. template.mapReduce(query, "collection", "function(){}", "function(key,values){}",
  512. new MapReduceOptions().limit(1000), Wrapper.class);
  513. verify(output, times(1)).limit(1000);
  514. }
  515. @Test // DATAMONGO-1639
  516. void beforeConvertEventForUpdateSeesNextVersion() {
  517. when(updateResult.getModifiedCount()).thenReturn(1L);
  518. final VersionedEntity entity = new VersionedEntity();
  519. entity.id = 1;
  520. entity.version = 0;
  521. GenericApplicationContext context = new GenericApplicationContext();
  522. context.refresh();
  523. context.addApplicationListener(new AbstractMongoEventListener<VersionedEntity>() {
  524. @Override
  525. public void onBeforeConvert(BeforeConvertEvent<VersionedEntity> event) {
  526. assertThat(event.getSource().version).isEqualTo(1);
  527. }
  528. });
  529. template.setApplicationContext(context);
  530. template.save(entity);
  531. }
  532. @Test // DATAMONGO-1447
  533. void shouldNotAppend$isolatedToNonMulitUpdate() {
  534. template.updateFirst(new Query(), new Update().isolated().set("jon", "snow"), Wrapper.class);
  535. ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class);
  536. ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class);
  537. verify(collection).updateOne(queryCaptor.capture(), updateCaptor.capture(), any());
  538. assertThat((Document) queryCaptor.getValue()).doesNotContainKey("$isolated");
  539. assertThat((Document) updateCaptor.getValue()).containsEntry("$set.jon", "snow").doesNotContainKey("$isolated");
  540. }
  541. @Test // DATAMONGO-1447
  542. void shouldAppend$isolatedToUpdateMultiEmptyQuery() {
  543. template.updateMulti(new Query(), new Update().isolated().set("jon", "snow"), Wrapper.class);
  544. ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class);
  545. ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class);
  546. verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any());
  547. assertThat((Document) queryCaptor.getValue()).hasSize(1).containsEntry("$isolated", 1);
  548. assertThat((Document) updateCaptor.getValue()).containsEntry("$set.jon", "snow").doesNotContainKey("$isolated");
  549. }
  550. @Test // DATAMONGO-1447
  551. void shouldAppend$isolatedToUpdateMultiQueryIfNotPresentAndUpdateSetsValue() {
  552. Update update = new Update().isolated().set("jon", "snow");
  553. Query query = new BasicQuery("{'eddard':'stark'}");
  554. template.updateMulti(query, update, Wrapper.class);
  555. ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class);
  556. ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class);
  557. verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any());
  558. assertThat((Document) queryCaptor.getValue()).containsEntry("$isolated", 1).containsEntry("eddard", "stark");
  559. assertThat((Document) updateCaptor.getValue()).containsEntry("$set.jon", "snow").doesNotContainKey("$isolated");
  560. }
  561. @Test // DATAMONGO-1447
  562. void shouldNotAppend$isolatedToUpdateMultiQueryIfNotPresentAndUpdateDoesNotSetValue() {
  563. Update update = new Update().set("jon", "snow");
  564. Query query = new BasicQuery("{'eddard':'stark'}");
  565. template.updateMulti(query, update, Wrapper.class);
  566. ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class);
  567. ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class);
  568. verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any());
  569. assertThat((Document) queryCaptor.getValue()).doesNotContainKey("$isolated").containsEntry("eddard", "stark");
  570. assertThat((Document) updateCaptor.getValue()).containsEntry("$set.jon", "snow").doesNotContainKey("$isolated");
  571. }
  572. @Test // DATAMONGO-1447
  573. void shouldNotOverwrite$isolatedToUpdateMultiQueryIfPresentAndUpdateDoesNotSetValue() {
  574. Update update = new Update().set("jon", "snow");
  575. Query query = new BasicQuery("{'eddard':'stark', '$isolated' : 1}");
  576. template.updateMulti(query, update, Wrapper.class);
  577. ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class);
  578. ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class);
  579. verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any());
  580. assertThat((Document) queryCaptor.getValue()).containsEntry("$isolated", 1).containsEntry("eddard", "stark");
  581. assertThat((Document) updateCaptor.getValue()).containsEntry("$set.jon", "snow").doesNotContainKey("$isolated");
  582. }
  583. @Test // DATAMONGO-1447
  584. void shouldNotOverwrite$isolatedToUpdateMultiQueryIfPresentAndUpdateSetsValue() {
  585. Update update = new Update().isolated().set("jon", "snow");
  586. Query query = new BasicQuery("{'eddard':'stark', '$isolated' : 0}");
  587. template.updateMulti(query, update, Wrapper.class);
  588. ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class);
  589. ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class);
  590. verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any());
  591. assertThat((Document) queryCaptor.getValue()).containsEntry("$isolated", 0).containsEntry("eddard", "stark");
  592. assertThat((Document) updateCaptor.getValue()).containsEntry("$set.jon", "snow").doesNotContainKey("$isolated");
  593. }
  594. @Test // DATAMONGO-1311
  595. void executeQueryShouldUseBatchSizeWhenPresent() {
  596. when(findIterable.batchSize(anyInt())).thenReturn(findIterable);
  597. Query query = new Query().cursorBatchSize(1234);
  598. template.find(query, Person.class);
  599. verify(findIterable).batchSize(1234);
  600. }
  601. @Test // DATAMONGO-1518
  602. void executeQueryShouldUseCollationWhenPresent() {
  603. template.executeQuery(new BasicQuery("{}").collation(Collation.of("fr")), "collection-1", val -> {});
  604. verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
  605. }
  606. @Test // DATAMONGO-1518
  607. void streamQueryShouldUseCollationWhenPresent() {
  608. template.stream(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class).next();
  609. verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
  610. }
  611. @Test // DATAMONGO-1518
  612. void findShouldUseCollationWhenPresent() {
  613. template.find(new BasicQuery("{'foo' : 'bar'}").collation(Collation.of("fr")), AutogenerateableId.class);
  614. verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
  615. }
  616. @Test // DATAMONGO-1518
  617. void findOneShouldUseCollationWhenPresent() {
  618. template.findOne(new BasicQuery("{'foo' : 'bar'}").collation(Collation.of("fr")), AutogenerateableId.class);
  619. verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
  620. }
  621. @Test // DATAMONGO-1518
  622. void existsShouldUseCollationWhenPresent() {
  623. template.exists(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class);
  624. ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
  625. verify(collection).countDocuments(any(), options.capture());
  626. assertThat(options.getValue().getCollation())
  627. .isEqualTo(com.mongodb.client.model.Collation.builder().locale("fr").build());
  628. }
  629. @Test // DATAMONGO-1518
  630. void findAndModfiyShoudUseCollationWhenPresent() {
  631. template.findAndModify(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class);
  632. ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
  633. verify(collection).findOneAndUpdate(any(), any(Bson.class), options.capture());
  634. assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
  635. }
  636. @Test // DATAMONGO-1518
  637. void findAndRemoveShouldUseCollationWhenPresent() {
  638. template.findAndRemove(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class);
  639. ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class);
  640. verify(collection).findOneAndDelete(any(), options.capture());
  641. assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
  642. }
  643. @Test // DATAMONGO-2196
  644. void removeShouldApplyWriteConcern() {
  645. Person person = new Person();
  646. person.id = "id-1";
  647. template.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
  648. template.remove(person);
  649. verify(collection).withWriteConcern(eq(WriteConcern.UNACKNOWLEDGED));
  650. verify(collectionWithWriteConcern).deleteOne(any(Bson.class), any());
  651. }
  652. @Test // DATAMONGO-1518
  653. void findAndRemoveManyShouldUseCollationWhenPresent() {
  654. template.doRemove("collection-1", new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class,
  655. true);
  656. ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
  657. verify(collection).deleteMany(any(), options.capture());
  658. assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
  659. }
  660. @Test // DATAMONGO-1518
  661. void updateOneShouldUseCollationWhenPresent() {
  662. template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update().set("foo", "bar"),
  663. AutogenerateableId.class);
  664. ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
  665. verify(collection).updateOne(any(), any(Bson.class), options.capture());
  666. assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
  667. }
  668. @Test // DATAMONGO-1518
  669. void updateManyShouldUseCollationWhenPresent() {
  670. template.updateMulti(new BasicQuery("{}").collation(Collation.of("fr")), new Update().set("foo", "bar"),
  671. AutogenerateableId.class);
  672. ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
  673. verify(collection).updateMany(any(), any(Bson.class), options.capture());
  674. assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
  675. }
  676. @Test // DATAMONGO-1518
  677. void replaceOneShouldUseCollationWhenPresent() {
  678. template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class);
  679. ArgumentCaptor<ReplaceOptions> options = ArgumentCaptor.forClass(ReplaceOptions.class);
  680. verify(collection).replaceOne(any(), any(), options.capture());
  681. assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
  682. }
  683. @Test // DATAMONGO-1518, DATAMONGO-1824
  684. void aggregateShouldUseCollationWhenPresent() {
  685. Aggregation aggregation = newAggregation(project("id"))
  686. .withOptions(newAggregationOptions().collation(Collation.of("fr")).build());
  687. template.aggregate(aggregation, AutogenerateableId.class, Document.class);
  688. verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
  689. }
  690. @Test // DATAMONGO-1824
  691. void aggregateShouldUseBatchSizeWhenPresent() {
  692. Aggregation aggregation = newAggregation(project("id"))
  693. .withOptions(newAggregationOptions().collation(Collation.of("fr")).cursorBatchSize(100).build());
  694. template.aggregate(aggregation, AutogenerateableId.class, Document.class);
  695. verify(aggregateIterable).batchSize(100);
  696. }
  697. @Test // DATAMONGO-1518
  698. void mapReduceShouldUseCollationWhenPresent() {
  699. template.mapReduce("", "", "", MapReduceOptions.options().collation(Collation.of("fr")), AutogenerateableId.class);
  700. verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
  701. }
  702. @Test // DATAMONGO-2027
  703. void mapReduceShouldUseOutputCollectionWhenPresent() {
  704. template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection"),
  705. AutogenerateableId.class);
  706. verify(mapReduceIterable).collectionName(eq("out-collection"));
  707. }
  708. @Test // DATAMONGO-2027
  709. void mapReduceShouldNotUseOutputCollectionForInline() {
  710. template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection").outputTypeInline(),
  711. AutogenerateableId.class);
  712. verify(mapReduceIterable, never()).collectionName(any());
  713. }
  714. @Test // DATAMONGO-2027
  715. void mapReduceShouldUseOutputActionWhenPresent() {
  716. template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection").outputTypeMerge(),
  717. AutogenerateableId.class);
  718. verify(mapReduceIterable).action(eq(MapReduceAction.MERGE));
  719. }
  720. @Test // DATAMONGO-2027
  721. void mapReduceShouldUseOutputDatabaseWhenPresent() {
  722. template.mapReduce("", "", "",
  723. MapReduceOptions.options().outputDatabase("out-database").outputCollection("out-collection").outputTypeMerge(),
  724. AutogenerateableId.class);
  725. verify(mapReduceIterable).databaseName(eq("out-database"));
  726. }
  727. @Test // DATAMONGO-2027
  728. void mapReduceShouldNotUseOutputDatabaseForInline() {
  729. template.mapReduce("", "", "", MapReduceOptions.options().outputDatabase("out-database").outputTypeInline(),
  730. AutogenerateableId.class);
  731. verify(mapReduceIterable, never()).databaseName(any());
  732. }
  733. @Test // DATAMONGO-1518, DATAMONGO-2264
  734. void geoNearShouldUseCollationWhenPresent() {
  735. NearQuery query = NearQuery.near(0D, 0D).query(new BasicQuery("{}").collation(Collation.of("fr")));
  736. template.geoNear(query, AutogenerateableId.class);
  737. verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
  738. }
  739. @Test // DATAMONGO-1518
  740. void groupShouldUseCollationWhenPresent() {
  741. commandResultDocument.append("retval", Collections.emptySet());
  742. template.group("collection-1", GroupBy.key("id").reduceFunction("bar").collation(Collation.of("fr")),
  743. AutogenerateableId.class);
  744. ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class);
  745. verify(db).runCommand(cmd.capture(), any(Class.class));
  746. assertThat(cmd.getValue().get("group", Document.class).get("collation", Document.class))
  747. .isEqualTo(new Document("locale", "fr"));
  748. }
  749. @Test // DATAMONGO-1880
  750. void countShouldUseCollationWhenPresent() {
  751. template.count(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class);
  752. ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
  753. verify(collection).countDocuments(any(), options.capture());
  754. assertThat(options.getValue().getCollation())
  755. .isEqualTo(com.mongodb.client.model.Collation.builder().locale("fr").build());
  756. }
  757. @Test // DATAMONGO-2360
  758. void countShouldApplyQueryHintIfPresent() {
  759. Document queryHint = new Document("age", 1);
  760. template.count(new BasicQuery("{}").withHint(queryHint), AutogenerateableId.class);
  761. ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
  762. verify(collection).countDocuments(any(), options.capture());
  763. assertThat(options.getValue().getHint()).isEqualTo(queryHint);
  764. }
  765. @Test // DATAMONGO-2365
  766. void countShouldApplyQueryHintAsIndexNameIfPresent() {
  767. template.count(new BasicQuery("{}").withHint("idx-1"), AutogenerateableId.class);
  768. ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
  769. verify(collection).countDocuments(any(), options.capture());
  770. assertThat(options.getValue().getHintString()).isEqualTo("idx-1");
  771. }
  772. @Test // DATAMONGO-1733
  773. void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() {
  774. template.doFind("star-wars", new Document(), new Document(), Person.class, PersonProjection.class,
  775. CursorPreparer.NO_OP_PREPARER);
  776. verify(findIterable).projection(eq(new Document("firstname", 1)));
  777. }
  778. @Test // DATAMONGO-1733
  779. void doesNotApplyFieldsWhenInterfaceProjectionIsClosedAndQueryDefinesFields() {
  780. template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, PersonProjection.class,
  781. CursorPreparer.NO_OP_PREPARER);
  782. verify(findIterable).projection(eq(new Document("bar", 1)));
  783. }
  784. @Test // DATAMONGO-1733
  785. void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
  786. template.doFind("star-wars", new Document(), new Document(), Person.class, PersonSpELProjection.class,
  787. CursorPreparer.NO_OP_PREPARER);
  788. verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
  789. }
  790. @Test // DATAMONGO-1733, DATAMONGO-2041
  791. void appliesFieldsToDtoProjection() {
  792. template.doFind("star-wars", new Document(), new Document(), Person.class, Jedi.class,
  793. CursorPreparer.NO_OP_PREPARER);
  794. verify(findIterable).projection(eq(new Document("firstname", 1)));
  795. }
  796. @Test // DATAMONGO-1733
  797. void doesNotApplyFieldsToDtoProjectionWhenQueryDefinesFields() {
  798. template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, Jedi.class,
  799. CursorPreparer.NO_OP_PREPARER);
  800. verify(findIterable).projection(eq(new Document("bar", 1)));
  801. }
  802. @Test // DATAMONGO-1733
  803. void doesNotApplyFieldsWhenTargetIsNotAProjection() {
  804. template.doFind("star-wars", new Document(), new Document(), Person.class, Person.class,
  805. CursorPreparer.NO_OP_PREPARER);
  806. verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
  807. }
  808. @Test // DATAMONGO-1733
  809. void doesNotApplyFieldsWhenTargetExtendsDomainType() {
  810. template.doFind("star-wars", new Document(), new Document(), Person.class, PersonExtended.class,
  811. CursorPreparer.NO_OP_PREPARER);
  812. verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
  813. }
  814. @Test // DATAMONGO-1348, DATAMONGO-2264
  815. void geoNearShouldMapQueryCorrectly() {
  816. NearQuery query = NearQuery.near(new Point(1, 1));
  817. query.query(Query.query(Criteria.where("customName").is("rand al'thor")));
  818. template.geoNear(query, WithNamedFields.class);
  819. ArgumentCaptor<List<Document>> capture = ArgumentCaptor.forClass(List.class);
  820. verify(collection).aggregate(capture.capture(), eq(Document.class));
  821. Document $geoNear = capture.getValue().iterator().next();
  822. assertThat($geoNear).containsEntry("$geoNear.query.custom-named-field", "rand al'thor")
  823. .doesNotContainKey("query.customName");
  824. }
  825. @Test // DATAMONGO-1348, DATAMONGO-2264
  826. void geoNearShouldMapGeoJsonPointCorrectly() {
  827. NearQuery query = NearQuery.near(new GeoJsonPoint(1, 2));
  828. query.query(Query.query(Criteria.where("customName").is("rand al'thor")));
  829. template.geoNear(query, WithNamedFields.class);
  830. ArgumentCaptor<List<Document>> capture = ArgumentCaptor.forClass(List.class);
  831. verify(collection).aggregate(capture.capture(), eq(Document.class));
  832. Document $geoNear = capture.getValue().iterator().next();
  833. assertThat($geoNear).containsEntry("$geoNear.near.type", "Point").containsEntry("$geoNear.near.coordinates.[0]", 1D)
  834. .containsEntry("$geoNear.near.coordinates.[1]", 2D);
  835. }
  836. @Test // DATAMONGO-2155, GH-3407
  837. void saveVersionedEntityShouldCallUpdateCorrectly() {
  838. when(updateResult.getModifiedCount()).thenReturn(1L);
  839. VersionedEntity entity = new VersionedEntity();
  840. entity.id = 1;
  841. entity.version = 10;
  842. ArgumentCaptor<org.bson.Document> queryCaptor = ArgumentCaptor.forClass(org.bson.Document.class);
  843. ArgumentCaptor<org.bson.Document> updateCaptor = ArgumentCaptor.forClass(org.bson.Document.class);
  844. template.save(entity);
  845. verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(), any(ReplaceOptions.class));
  846. assertThat(queryCaptor.getValue()).isEqualTo(new Document("_id", 1).append("version", 10));
  847. assertThat(updateCaptor.getValue())
  848. .isEqualTo(new Document("version", 11).append("_class", VersionedEntity.class.getName()).append("name", null));
  849. }
  850. @Test // DATAMONGO-1783
  851. void usesQueryOffsetForCountOperation() {
  852. template.count(new BasicQuery("{}").skip(100), AutogenerateableId.class);
  853. ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
  854. verify(collection).countDocuments(any(), options.capture());
  855. assertThat(options.getValue().getSkip()).isEqualTo(100);
  856. }
  857. @Test // DATAMONGO-1783
  858. void usesQueryLimitForCountOperation() {
  859. template.count(new BasicQuery("{}").limit(10), AutogenerateableId.class);
  860. ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
  861. verify(collection).countDocuments(any(), options.capture());
  862. assertThat(options.getValue().getLimit()).isEqualTo(10);
  863. }
  864. @Test // DATAMONGO-2215
  865. void updateShouldApplyArrayFilters() {
  866. template.updateFirst(new BasicQuery("{}"),
  867. new Update().set("grades.$[element]", 100).filterArray(Criteria.where("element").gte(100)),
  868. EntityWithListOfSimple.class);
  869. ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
  870. verify(collection).updateOne(any(), any(Bson.class), options.capture());
  871. Assertions.assertThat((List<Bson>) options.getValue().getArrayFilters())
  872. .contains(new org.bson.Document("element", new Document("$gte", 100)));
  873. }
  874. @Test // DATAMONGO-2215
  875. void findAndModifyShouldApplyArrayFilters() {
  876. template.findAndModify(new BasicQuery("{}"),
  877. new Update().set("grades.$[element]", 100).filterArray(Criteria.where("element").gte(100)),
  878. EntityWithListOfSimple.class);
  879. ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
  880. verify(collection).findOneAndUpdate(any(), any(Bson.class), options.capture());
  881. Assertions.assertThat((List<Bson>) options.getValue().getArrayFilters())
  882. .contains(new org.bson.Document("element", new Document("$gte", 100)));
  883. }
  884. @Test // DATAMONGO-1854
  885. void streamQueryShouldUseDefaultCollationWhenPresent() {
  886. template.stream(new BasicQuery("{}"), Sith.class).next();
  887. verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
  888. }
  889. @Test // DATAMONGO-1854
  890. void findShouldNotUseCollationWhenNoDefaultPresent() {
  891. template.find(new BasicQuery("{'foo' : 'bar'}"), Jedi.class);
  892. verify(findIterable, never()).collation(any());
  893. }
  894. @Test // DATAMONGO-1854
  895. void findShouldUseDefaultCollationWhenPresent() {
  896. template.find(new BasicQuery("{'foo' : 'bar'}"), Sith.class);
  897. verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
  898. }
  899. @Test // DATAMONGO-1854
  900. void findOneShouldUseDefaultCollationWhenPresent() {
  901. template.findOne(new BasicQuery("{'foo' : 'bar'}"), Sith.class);
  902. verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
  903. }
  904. @Test // DATAMONGO-1854
  905. void existsShouldUseDefaultCollationWhenPresent() {
  906. template.exists(new BasicQuery("{}"), Sith.class);
  907. ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
  908. verify(collection).countDocuments(any(), options.capture());
  909. assertThat(options.getValue().getCollation())
  910. .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
  911. }
  912. @Test // DATAMONGO-1854
  913. void findAndModfiyShoudUseDefaultCollationWhenPresent() {
  914. template.findAndModify(new BasicQuery("{}"), new Update(), Sith.class);
  915. ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
  916. verify(collection).findOneAndUpdate(any(), any(Bson.class), options.capture());
  917. assertThat(options.getValue().getCollation())
  918. .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
  919. }
  920. @Test // DATAMONGO-1854
  921. void findAndRemoveShouldUseDefaultCollationWhenPresent() {
  922. template.findAndRemove(new BasicQuery("{}"), Sith.class);
  923. ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class);
  924. verify(collection).findOneAndDelete(any(), options.capture());
  925. assertThat(options.getValue().getCollation())
  926. .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
  927. }
  928. @Test // DATAMONGO-1854
  929. void createCollectionShouldNotCollationIfNotPresent() {
  930. template.createCollection(AutogenerateableId.class);
  931. ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
  932. verify(db).createCollection(any(), options.capture());
  933. Assertions.assertThat(options.getValue().getCollation()).isNull();
  934. }
  935. @Test // DATAMONGO-1854
  936. void createCollectionShouldApplyDefaultCollation() {
  937. template.createCollection(Sith.class);
  938. ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
  939. verify(db).createCollection(any(), options.capture());
  940. assertThat