/* * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.mongodb.core; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.mockito.Mockito.any; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import java.math.BigInteger; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.regex.Pattern; import org.bson.Document; import org.bson.conversions.Bson; import org.bson.types.ObjectId; import org.hamcrest.collection.IsIterableContainingInOrder; import org.hamcrest.core.Is; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; import org.springframework.data.convert.CustomConversions; import org.springframework.data.domain.Sort; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; import org.springframework.data.mongodb.core.mapreduce.GroupBy; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.test.util.ReflectionTestUtils; import com.mongodb.DB; import com.mongodb.MongoClient; import com.mongodb.MongoException; import com.mongodb.ReadPreference; import com.mongodb.client.FindIterable; import com.mongodb.client.MapReduceIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.DeleteOptions; import com.mongodb.client.model.FindOneAndDeleteOptions; import com.mongodb.client.model.FindOneAndUpdateOptions; import com.mongodb.client.model.UpdateOptions; import com.mongodb.client.result.UpdateResult; /** * Unit tests for {@link MongoTemplate}. * * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class MongoTemplateUnitTests extends MongoOperationsUnitTests { MongoTemplate template; @Mock MongoDbFactory factory; @Mock MongoClient mongo; @Mock MongoDatabase db; @Mock MongoCollection<Document> collection; @Mock MongoCursor<Document> cursor; @Mock FindIterable<Document> findIterable; @Mock MapReduceIterable mapReduceIterable; Document commandResultDocument = new Document(); MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator(); MappingMongoConverter converter; MongoMappingContext mappingContext; @Before public void setUp() { when(findIterable.iterator()).thenReturn(cursor); when(factory.getDb()).thenReturn(db); when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator); when(db.getCollection(Mockito.any(String.class), eq(Document.class))).thenReturn(collection); when(db.runCommand(Mockito.any(), Mockito.any(Class.class))).thenReturn(commandResultDocument); when(collection.find(Mockito.any(org.bson.Document.class))).thenReturn(findIterable); when(collection.mapReduce(Mockito.any(), Mockito.any())).thenReturn(mapReduceIterable); when(findIterable.projection(Mockito.any())).thenReturn(findIterable); when(findIterable.sort(Mockito.any(org.bson.Document.class))).thenReturn(findIterable); when(findIterable.modifiers(Mockito.any(org.bson.Document.class))).thenReturn(findIterable); when(findIterable.collation(Mockito.any())).thenReturn(findIterable); when(findIterable.limit(anyInt())).thenReturn(findIterable); when(mapReduceIterable.collation(Mockito.any())).thenReturn(mapReduceIterable); when(mapReduceIterable.iterator()).thenReturn(cursor); this.mappingContext = new MongoMappingContext(); this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext); this.template = new MongoTemplate(factory, converter); } @Test(expected = IllegalArgumentException.class) public void rejectsNullDatabaseName() throws Exception { new MongoTemplate(mongo, null); } @Test(expected = IllegalArgumentException.class) public void rejectsNullMongo() throws Exception { new MongoTemplate(null, "database"); } @Test(expected = DataAccessException.class) public void removeHandlesMongoExceptionProperly() throws Exception { MongoTemplate template = mockOutGetDb(); template.remove(null, "collection"); } @Test public void defaultsConverterToMappingMongoConverter() throws Exception { MongoTemplate template = new MongoTemplate(mongo, "database"); assertTrue(ReflectionTestUtils.getField(template, "mongoConverter") instanceof MappingMongoConverter); } @Test(expected = InvalidDataAccessApiUsageException.class) public void rejectsNotFoundMapReduceResource() { GenericApplicationContext ctx = new GenericApplicationContext(); ctx.refresh(); template.setApplicationContext(ctx); template.mapReduce("foo", "classpath:doesNotExist.js", "function() {}", Person.class); } @Test(expected = InvalidDataAccessApiUsageException.class) // DATAMONGO-322 public void rejectsEntityWithNullIdIfNotSupportedIdType() { Object entity = new NotAutogenerateableId(); template.save(entity); } @Test // DATAMONGO-322 public void storesEntityWithSetIdAlthoughNotAutogenerateable() { NotAutogenerateableId entity = new NotAutogenerateableId(); entity.id = 1; template.save(entity); } @Test // DATAMONGO-322 public void autogeneratesIdForEntityWithAutogeneratableId() { this.converter.afterPropertiesSet(); MongoTemplate template = spy(this.template); doReturn(new ObjectId()).when(template).saveDocument(Mockito.any(String.class), Mockito.any(Document.class), Mockito.any(Class.class)); AutogenerateableId entity = new AutogenerateableId(); template.save(entity); assertThat(entity.id, is(notNullValue())); } @Test // DATAMONGO-374 public void convertsUpdateConstraintsUsingConverters() { CustomConversions conversions = new MongoCustomConversions(Collections.singletonList(MyConverter.INSTANCE)); this.converter.setCustomConversions(conversions); this.converter.afterPropertiesSet(); Query query = new Query(); Update update = new Update().set("foo", new AutogenerateableId()); template.updateFirst(query, update, Wrapper.class); QueryMapper queryMapper = new QueryMapper(converter); Document reference = queryMapper.getMappedObject(update.getUpdateObject(), Optional.empty()); verify(collection, times(1)).updateOne(Mockito.any(org.bson.Document.class), eq(reference), Mockito.any(UpdateOptions.class)); // .update(Mockito.any(Document.class), eq(reference), anyBoolean(), // anyBoolean()); } @Test // DATAMONGO-474 public void setsUnpopulatedIdField() { NotAutogenerateableId entity = new NotAutogenerateableId(); template.populateIdIfNecessary(entity, 5); assertThat(entity.id, is(5)); } @Test // DATAMONGO-474 public void doesNotSetAlreadyPopulatedId() { NotAutogenerateableId entity = new NotAutogenerateableId(); entity.id = 5; template.populateIdIfNecessary(entity, 7); assertThat(entity.id, is(5)); } @Test // DATAMONGO-868 public void findAndModifyShouldBumpVersionByOneWhenVersionFieldNotIncludedInUpdate() { VersionedEntity v = new VersionedEntity(); v.id = 1; v.version = 0; ArgumentCaptor<org.bson.Document> captor = ArgumentCaptor.forClass(org.bson.Document.class); template.findAndModify(new Query(), new Update().set("id", "10"), VersionedEntity.class); // verify(collection, times(1)).findAndModify(Matchers.any(Document.class), // org.mockito.Matchers.isNull(Document.class), org.mockito.Matchers.isNull(Document.class), eq(false), // captor.capture(), eq(false), eq(false)); verify(collection, times(1)).findOneAndUpdate(Matchers.any(org.bson.Document.class), captor.capture(), Matchers.any(FindOneAndUpdateOptions.class)); Assert.assertThat(captor.getValue().get("$inc"), Is.<Object> is(new org.bson.Document("version", 1L))); } @Test // DATAMONGO-868 public void findAndModifyShouldNotBumpVersionByOneWhenVersionFieldAlreadyIncludedInUpdate() { VersionedEntity v = new VersionedEntity(); v.id = 1; v.version = 0; ArgumentCaptor<org.bson.Document> captor = ArgumentCaptor.forClass(org.bson.Document.class); template.findAndModify(new Query(), new Update().set("version", 100), VersionedEntity.class); verify(collection, times(1)).findOneAndUpdate(Matchers.any(org.bson.Document.class), captor.capture(), Matchers.any(FindOneAndUpdateOptions.class)); // verify(collection, times(1)).findAndModify(Matchers.any(Document.class), isNull(Document.class), // isNull(Document.class), eq(false), captor.capture(), eq(false), eq(false)); Assert.assertThat(captor.getValue().get("$set"), Is.<Object> is(new org.bson.Document("version", 100))); Assert.assertThat(captor.getValue().get("$inc"), nullValue()); } @Test // DATAMONGO-533 public void registersDefaultEntityIndexCreatorIfApplicationContextHasOneForDifferentMappingContext() { GenericApplicationContext applicationContext = new GenericApplicationContext(); applicationContext.getBeanFactory().registerSingleton("foo", new MongoPersistentEntityIndexCreator(new MongoMappingContext(), template)); applicationContext.refresh(); GenericApplicationContext spy = spy(applicationContext); MongoTemplate mongoTemplate = new MongoTemplate(factory, converter); mongoTemplate.setApplicationContext(spy); verify(spy, times(1)).addApplicationListener(argThat(new ArgumentMatcher<MongoPersistentEntityIndexCreator>() { @Override public boolean matches(MongoPersistentEntityIndexCreator argument) { return argument.isIndexCreatorFor(mappingContext); } })); } @Test // DATAMONGO-566 public void findAllAndRemoveShouldRetrieveMatchingDocumentsPriorToRemoval() { BasicQuery query = new BasicQuery("{'foo':'bar'}"); template.findAllAndRemove(query, VersionedEntity.class); verify(collection, times(1)).find(Matchers.eq(query.getQueryObject())); } @Test // DATAMONGO-566 public void findAllAndRemoveShouldRemoveDocumentsReturedByFindQuery() { Mockito.when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false); Mockito.when(cursor.next()).thenReturn(new org.bson.Document("_id", Integer.valueOf(0))) .thenReturn(new org.bson.Document("_id", Integer.valueOf(1))); ArgumentCaptor<org.bson.Document> queryCaptor = ArgumentCaptor.forClass(org.bson.Document.class); BasicQuery query = new BasicQuery("{'foo':'bar'}"); template.findAllAndRemove(query, VersionedEntity.class); verify(collection, times(1)).deleteMany(queryCaptor.capture(), Mockito.any()); Document idField = DocumentTestUtils.getAsDocument(queryCaptor.getValue(), "_id"); assertThat((List<Object>) idField.get("$in"), IsIterableContainingInOrder.<Object> contains(Integer.valueOf(0), Integer.valueOf(1))); } @Test // DATAMONGO-566 public void findAllAndRemoveShouldNotTriggerRemoveIfFindResultIsEmpty() { template.findAllAndRemove(new BasicQuery("{'foo':'bar'}"), VersionedEntity.class); verify(collection, never()).deleteMany(Mockito.any(org.bson.Document.class)); } @Test // DATAMONGO-948 public void sortShouldBeTakenAsIsWhenExecutingQueryWithoutSpecificTypeInformation() { Query query = Query.query(Criteria.where("foo").is("bar")).with(Sort.by("foo")); template.executeQuery(query, "collection1", new DocumentCallbackHandler() { @Override public void processDocument(Document document) throws MongoException, DataAccessException { // nothing to do - just a test } }); ArgumentCaptor<org.bson.Document> captor = ArgumentCaptor.forClass(org.bson.Document.class); verify(findIterable, times(1)).sort(captor.capture()); assertThat(captor.getValue(), equalTo(new org.bson.Document("foo", 1))); } @Test // DATAMONGO-1166 public void aggregateShouldHonorReadPreferenceWhenSet() { when(db.runCommand(Mockito.any(org.bson.Document.class), Mockito.any(ReadPreference.class), eq(Document.class))) .thenReturn(mock(Document.class)); template.setReadPreference(ReadPreference.secondary()); template.aggregate(newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class); verify(this.db, times(1)).runCommand(Mockito.any(org.bson.Document.class), eq(ReadPreference.secondary()), eq(Document.class)); } @Test // DATAMONGO-1166 public void aggregateShouldIgnoreReadPreferenceWhenNotSet() { when(db.runCommand(Mockito.any(org.bson.Document.class), eq(org.bson.Document.class))) .thenReturn(mock(Document.class)); template.aggregate(newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class); verify(this.db, times(1)).runCommand(Mockito.any(org.bson.Document.class), eq(org.bson.Document.class)); } @Test // DATAMONGO-1166 public void geoNearShouldHonorReadPreferenceWhenSet() { when(db.runCommand(Mockito.any(org.bson.Document.class), Mockito.any(ReadPreference.class), eq(Document.class))) .thenReturn(mock(Document.class)); template.setReadPreference(ReadPreference.secondary()); NearQuery query = NearQuery.near(new Point(1, 1)); template.geoNear(query, Wrapper.class); verify(this.db, times(1)).runCommand(Mockito.any(org.bson.Document.class), eq(ReadPreference.secondary()), eq(Document.class)); } @Test // DATAMONGO-1166 public void geoNearShouldIgnoreReadPreferenceWhenNotSet() { when(db.runCommand(Mockito.any(Document.class), eq(Document.class))).thenReturn(mock(Document.class)); NearQuery query = NearQuery.near(new Point(1, 1)); template.geoNear(query, Wrapper.class); verify(this.db, times(1)).runCommand(Mockito.any(Document.class), eq(Document.class)); } @Test // DATAMONGO-1334 @Ignore("TODO: mongo3 - a bit hard to tests with the immutable object stuff") public void mapReduceShouldUseZeroAsDefaultLimit() { MongoCursor cursor = mock(MongoCursor.class); MapReduceIterable output = mock(MapReduceIterable.class); when(output.limit(anyInt())).thenReturn(output); when(output.sort(Mockito.any(Document.class))).thenReturn(output); when(output.filter(Mockito.any(Document.class))).thenReturn(output); when(output.iterator()).thenReturn(cursor); when(cursor.hasNext()).thenReturn(false); when(collection.mapReduce(anyString(), anyString())).thenReturn(output); Query query = new BasicQuery("{'foo':'bar'}"); template.mapReduce(query, "collection", "function(){}", "function(key,values){}", Wrapper.class); verify(output, times(1)).limit(1); } @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromQuery() { MongoCursor cursor = mock(MongoCursor.class); MapReduceIterable output = mock(MapReduceIterable.class); when(output.limit(anyInt())).thenReturn(output); when(output.sort(Mockito.any(Document.class))).thenReturn(output); when(output.filter(Mockito.any(Document.class))).thenReturn(output); when(output.iterator()).thenReturn(cursor); when(cursor.hasNext()).thenReturn(false); when(collection.mapReduce(anyString(), anyString())).thenReturn(output); Query query = new BasicQuery("{'foo':'bar'}"); query.limit(100); template.mapReduce(query, "collection", "function(){}", "function(key,values){}", Wrapper.class); verify(output, times(1)).limit(100); } @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptions() { MongoCursor cursor = mock(MongoCursor.class); MapReduceIterable output = mock(MapReduceIterable.class); when(output.limit(anyInt())).thenReturn(output); when(output.sort(Mockito.any(Document.class))).thenReturn(output); when(output.filter(Mockito.any(Document.class))).thenReturn(output); when(output.iterator()).thenReturn(cursor); when(cursor.hasNext()).thenReturn(false); when(collection.mapReduce(anyString(), anyString())).thenReturn(output); Query query = new BasicQuery("{'foo':'bar'}"); template.mapReduce(query, "collection", "function(){}", "function(key,values){}", new MapReduceOptions().limit(1000), Wrapper.class); verify(output, times(1)).limit(1000); } @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptionsWhenQueryIsNotPresent() { MongoCursor cursor = mock(MongoCursor.class); MapReduceIterable output = mock(MapReduceIterable.class); when(output.limit(anyInt())).thenReturn(output); when(output.iterator()).thenReturn(cursor); when(cursor.hasNext()).thenReturn(false); when(collection.mapReduce(anyString(), anyString())).thenReturn(output); template.mapReduce("collection", "function(){}", "function(key,values){}", new MapReduceOptions().limit(1000), Wrapper.class); verify(output, times(1)).limit(1000); } @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptionsEvenWhenQueryDefinesItDifferently() { MongoCursor cursor = mock(MongoCursor.class); MapReduceIterable output = mock(MapReduceIterable.class); when(output.limit(anyInt())).thenReturn(output); when(output.sort(Mockito.any(Document.class))).thenReturn(output); when(output.filter(Mockito.any(Document.class))).thenReturn(output); when(output.iterator()).thenReturn(cursor); when(cursor.hasNext()).thenReturn(false); when(collection.mapReduce(anyString(), anyString())).thenReturn(output); Query query = new BasicQuery("{'foo':'bar'}"); query.limit(100); template.mapReduce(query, "collection", "function(){}", "function(key,values){}", new MapReduceOptions().limit(1000), Wrapper.class); verify(output, times(1)).limit(1000); } @Test // DATAMONGO-1639 public void beforeConvertEventForUpdateSeesNextVersion() { final VersionedEntity entity = new VersionedEntity(); entity.id = 1; entity.version = 0; GenericApplicationContext context = new GenericApplicationContext(); context.refresh(); context.addApplicationListener(new AbstractMongoEventListener<VersionedEntity>() { @Override public void onBeforeConvert(BeforeConvertEvent<VersionedEntity> event) { assertThat(event.getSource().version, is(1)); } }); template.setApplicationContext(context); MongoTemplate spy = Mockito.spy(template); UpdateResult result = mock(UpdateResult.class); doReturn(1L).when(result).getModifiedCount(); doReturn(result).when(spy).doUpdate(anyString(), Mockito.any(Query.class), Mockito.any(Update.class), Mockito.any(Class.class), anyBoolean(), anyBoolean()); spy.save(entity); } @Test // DATAMONGO-1447 public void shouldNotAppend$isolatedToNonMulitUpdate() { template.updateFirst(new Query(), new Update().isolated().set("jon", "snow"), Wrapper.class); ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class); ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class); verify(collection).updateOne(queryCaptor.capture(), updateCaptor.capture(), any()); assertThat(queryCaptor.getValue(), isBsonObject().notContaining("$isolated")); assertThat(updateCaptor.getValue(), isBsonObject().containing("$set.jon", "snow").notContaining("$isolated")); } @Test // DATAMONGO-1447 public void shouldAppend$isolatedToUpdateMultiEmptyQuery() { template.updateMulti(new Query(), new Update().isolated().set("jon", "snow"), Wrapper.class); ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class); ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class); verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any()); assertThat(queryCaptor.getValue(), isBsonObject().withSize(1).containing("$isolated", 1)); assertThat(updateCaptor.getValue(), isBsonObject().containing("$set.jon", "snow").notContaining("$isolated")); } @Test // DATAMONGO-1447 public void shouldAppend$isolatedToUpdateMultiQueryIfNotPresentAndUpdateSetsValue() { Update update = new Update().isolated().set("jon", "snow"); Query query = new BasicQuery("{'eddard':'stark'}"); template.updateMulti(query, update, Wrapper.class); ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class); ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class); verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any()); assertThat(queryCaptor.getValue(), isBsonObject().containing("$isolated", 1).containing("eddard", "stark")); assertThat(updateCaptor.getValue(), isBsonObject().containing("$set.jon", "snow").notContaining("$isolated")); } @Test // DATAMONGO-1447 public void shouldNotAppend$isolatedToUpdateMultiQueryIfNotPresentAndUpdateDoesNotSetValue() { Update update = new Update().set("jon", "snow"); Query query = new BasicQuery("{'eddard':'stark'}"); template.updateMulti(query, update, Wrapper.class); ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class); ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class); verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any()); assertThat(queryCaptor.getValue(), isBsonObject().notContaining("$isolated").containing("eddard", "stark")); assertThat(updateCaptor.getValue(), isBsonObject().containing("$set.jon", "snow").notContaining("$isolated")); } @Test // DATAMONGO-1447 public void shouldNotOverwrite$isolatedToUpdateMultiQueryIfPresentAndUpdateDoesNotSetValue() { Update update = new Update().set("jon", "snow"); Query query = new BasicQuery("{'eddard':'stark', '$isolated' : 1}"); template.updateMulti(query, update, Wrapper.class); ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class); ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class); verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any()); assertThat(queryCaptor.getValue(), isBsonObject().containing("$isolated", 1).containing("eddard", "stark")); assertThat(updateCaptor.getValue(), isBsonObject().containing("$set.jon", "snow").notContaining("$isolated")); } @Test // DATAMONGO-1447 public void shouldNotOverwrite$isolatedToUpdateMultiQueryIfPresentAndUpdateSetsValue() { Update update = new Update().isolated().set("jon", "snow"); Query query = new BasicQuery("{'eddard':'stark', '$isolated' : 0}"); template.updateMulti(query, update, Wrapper.class); ArgumentCaptor<Bson> queryCaptor = ArgumentCaptor.forClass(Bson.class); ArgumentCaptor<Bson> updateCaptor = ArgumentCaptor.forClass(Bson.class); verify(collection).updateMany(queryCaptor.capture(), updateCaptor.capture(), any()); assertThat(queryCaptor.getValue(), isBsonObject().containing("$isolated", 0).containing("eddard", "stark")); assertThat(updateCaptor.getValue(), isBsonObject().containing("$set.jon", "snow").notContaining("$isolated")); } @Test // DATAMONGO-1518 public void executeQueryShouldUseCollationWhenPresent() { template.executeQuery(new BasicQuery("{}").collation(Collation.of("fr")), "collection-1", val -> {}); verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build())); } @Test // DATAMONGO-1518 public void streamQueryShouldUseCollationWhenPresent() { template.stream(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class).next(); verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build())); } @Test // DATAMONGO-1518 public void findShouldUseCollationWhenPresent() { template.find(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class); verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build())); } @Test // DATAMONGO-1518 public void findOneShouldUseCollationWhenPresent() { template.findOne(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class); verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build())); } @Test // DATAMONGO-1518 public void existsShouldUseCollationWhenPresent() { template.exists(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class); verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build())); } @Test // DATAMONGO-1518 public void findAndModfiyShoudUseCollationWhenPresent() { template.findAndModify(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class); ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class); verify(collection).findOneAndUpdate(Mockito.any(), Mockito.any(), options.capture()); assertThat(options.getValue().getCollation().getLocale(), is("fr")); } @Test // DATAMONGO-1518 public void findAndRemoveShouldUseCollationWhenPresent() { template.findAndRemove(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class); ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class); verify(collection).findOneAndDelete(Mockito.any(), options.capture()); assertThat(options.getValue().getCollation().getLocale(), is("fr")); } @Test // DATAMONGO-1518 public void findAndRemoveManyShouldUseCollationWhenPresent() { template.doRemove("collection-1", new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class); ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class); verify(collection).deleteMany(Mockito.any(), options.capture()); assertThat(options.getValue().getCollation().getLocale(), is("fr")); } @Test // DATAMONGO-1518 public void updateOneShouldUseCollationWhenPresent() { template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update().set("foo", "bar"), AutogenerateableId.class); ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class); verify(collection).updateOne(Mockito.any(), Mockito.any(), options.capture()); assertThat(options.getValue().getCollation().getLocale(), is("fr")); } @Test // DATAMONGO-1518 public void updateManyShouldUseCollationWhenPresent() { template.updateMulti(new BasicQuery("{}").collation(Collation.of("fr")), new Update().set("foo", "bar"), AutogenerateableId.class); ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class); verify(collection).updateMany(Mockito.any(), Mockito.any(), options.capture()); assertThat(options.getValue().getCollation().getLocale(), is("fr")); } @Test // DATAMONGO-1518 public void replaceOneShouldUseCollationWhenPresent() { template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class); ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class); verify(collection).replaceOne(Mockito.any(), Mockito.any(), options.capture()); assertThat(options.getValue().getCollation().getLocale(), is("fr")); } @Test // DATAMONGO-1518 public void aggregateShouldUseCollationWhenPresent() { Aggregation aggregation = newAggregation(project("id")) .withOptions(newAggregationOptions().collation(Collation.of("fr")).build()); template.aggregate(aggregation, AutogenerateableId.class, Document.class); ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class); verify(db).runCommand(cmd.capture(), Mockito.any(Class.class)); assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr"))); } @Test // DATAMONGO-1518 public void mapReduceShouldUseCollationWhenPresent() { template.mapReduce("", "", "", MapReduceOptions.options().collation(Collation.of("fr")), AutogenerateableId.class); verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build())); } @Test // DATAMONGO-1518 public void geoNearShouldUseCollationWhenPresent() { NearQuery query = NearQuery.near(0D, 0D).query(new BasicQuery("{}").collation(Collation.of("fr"))); template.geoNear(query, AutogenerateableId.class); ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class); verify(db).runCommand(cmd.capture(), Mockito.any(Class.class)); assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr"))); } @Test // DATAMONGO-1518 public void groupShouldUseCollationWhenPresent() { commandResultDocument.append("retval", Collections.emptySet()); template.group("collection-1", GroupBy.key("id").reduceFunction("bar").collation(Collation.of("fr")), AutogenerateableId.class); ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class); verify(db).runCommand(cmd.capture(), Mockito.any(Class.class)); assertThat(cmd.getValue().get("group", Document.class).get("collation", Document.class), equalTo(new Document("locale", "fr"))); } class AutogenerateableId { @Id BigInteger id; } class NotAutogenerateableId { @Id Integer id; public Pattern getId() { return Pattern.compile("."); } } static class VersionedEntity { @Id Integer id; @Version Integer version; } enum MyConverter implements Converter<AutogenerateableId, String> { INSTANCE; public String convert(AutogenerateableId source) { return source.toString(); } } class Wrapper { AutogenerateableId foo; } /** * Mocks out the {@link MongoTemplate#getDb()} method to return the {@link DB} mock instead of executing the actual * behaviour. * * @return */ private MongoTemplate mockOutGetDb() { MongoTemplate template = spy(this.template); when(template.getDb()).thenReturn(db); return template; } /* (non-Javadoc) * @see org.springframework.data.mongodb.core.core.MongoOperationsUnitTests#getOperations() */ @Override protected MongoOperations getOperationsForExceptionHandling() { MongoTemplate template = spy(this.template); when(template.getDb()).thenThrow(new MongoException("Error!")); return template; } /* (non-Javadoc) * @see org.springframework.data.mongodb.core.core.MongoOperationsUnitTests#getOperations() */ @Override protected MongoOperations getOperations() { return this.template; } }