/*
* Copyright 2014-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.repository.query;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.hamcrest.core.Is;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.Person;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.Meta;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import com.mongodb.WriteResult;
import com.mongodb.client.result.DeleteResult;
/**
* Unit tests for {@link AbstractMongoQuery}.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class AbstractMongoQueryUnitTests {
@Mock MongoOperations mongoOperationsMock;
@Mock BasicMongoPersistentEntity<?> persitentEntityMock;
@Mock MongoMappingContext mappingContextMock;
@Mock WriteResult writeResultMock;
@Mock DeleteResult deleteResultMock;
@Before
public void setUp() {
doReturn("persons").when(persitentEntityMock).getCollection();
doReturn(Optional.of(persitentEntityMock)).when(mappingContextMock).getPersistentEntity(Mockito.any(Class.class));
doReturn(persitentEntityMock).when(mappingContextMock).getRequiredPersistentEntity(Mockito.any(Class.class));
doReturn(Person.class).when(persitentEntityMock).getType();
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDbFactory.class));
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContextMock);
converter.afterPropertiesSet();
doReturn(converter).when(mongoOperationsMock).getConverter();
}
@SuppressWarnings("unchecked")
@Test // DATAMONGO-566
public void testDeleteExecutionCallsRemoveCorreclty() {
createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" });
verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons"));
verify(mongoOperationsMock, times(0)).find(Matchers.any(Query.class), Matchers.any(Class.class),
Matchers.anyString());
}
@SuppressWarnings("unchecked")
@Test // DATAMONGO-566, DATAMONGO-1040
public void testDeleteExecutionLoadsListOfRemovedDocumentsWhenReturnTypeIsCollectionLike() {
createQueryForMethod("deleteByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" });
verify(mongoOperationsMock, times(1)).findAllAndRemove(Mockito.any(Query.class), eq(Person.class), eq("persons"));
}
@Test // DATAMONGO-566
public void testDeleteExecutionReturnsZeroWhenWriteResultIsNull() {
MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class);
query.setDeleteQuery(true);
assertThat(query.execute(new Object[] { "fake" }), Is.<Object> is(0L));
}
@Test // DATAMONGO-566, DATAMONGO-978
public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() {
when(deleteResultMock.getDeletedCount()).thenReturn(100L);
when(mongoOperationsMock.remove(Matchers.any(Query.class), eq(Person.class), eq("persons")))
.thenReturn(deleteResultMock);
MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class);
query.setDeleteQuery(true);
assertThat(query.execute(new Object[] { "fake" }), is((Object) 100L));
verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons"));
}
@Test // DATAMONGO-957
public void metadataShouldNotBeAddedToQueryWhenNotPresent() {
MongoQueryFake query = createQueryForMethod("findByFirstname", String.class);
query.execute(new Object[] { "fake" });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getValue().getMeta().getComment(), nullValue());
}
@Test // DATAMONGO-957
public void metadataShouldBeAddedToQueryCorrectly() {
MongoQueryFake query = createQueryForMethod("findByFirstname", String.class, Pageable.class);
query.execute(new Object[] { "fake", PageRequest.of(0, 10) });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(this.mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getValue().getMeta().getComment(), is("comment"));
}
@Test // DATAMONGO-957
public void metadataShouldBeAddedToCountQueryCorrectly() {
MongoQueryFake query = createQueryForMethod("findByFirstname", String.class, Pageable.class);
query.execute(new Object[] { "fake", PageRequest.of(1, 10) });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(mongoOperationsMock, times(1)).count(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getValue().getMeta().getComment(), is("comment"));
}
@Test // DATAMONGO-957
public void metadataShouldBeAddedToStringBasedQueryCorrectly() {
MongoQueryFake query = createQueryForMethod("findByAnnotatedQuery", String.class, Pageable.class);
query.execute(new Object[] { "fake", PageRequest.of(0, 10) });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(this.mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getValue().getMeta().getComment(), is("comment"));
}
@Test // DATAMONGO-1057
public void slicedExecutionShouldRetainNrOfElementsToSkip() {
MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class);
Pageable page1 = PageRequest.of(0, 10);
Pageable page2 = page1.next();
query.execute(new Object[] { "fake", page1 });
query.execute(new Object[] { "fake", page2 });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getAllValues().get(0).getSkip(), is(0L));
assertThat(captor.getAllValues().get(1).getSkip(), is(10L));
}
@Test // DATAMONGO-1057
public void slicedExecutionShouldIncrementLimitByOne() {
MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class);
Pageable page1 = PageRequest.of(0, 10);
Pageable page2 = page1.next();
query.execute(new Object[] { "fake", page1 });
query.execute(new Object[] { "fake", page2 });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getAllValues().get(0).getLimit(), is(11));
assertThat(captor.getAllValues().get(1).getLimit(), is(11));
}
@Test // DATAMONGO-1057
public void slicedExecutionShouldRetainSort() {
MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class);
Pageable page1 = PageRequest.of(0, 10, Sort.Direction.DESC, "bar");
Pageable page2 = page1.next();
query.execute(new Object[] { "fake", page1 });
query.execute(new Object[] { "fake", page2 });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
Document expectedSortObject = new Document().append("bar", -1);
assertThat(captor.getAllValues().get(0).getSortObject(), is(expectedSortObject));
assertThat(captor.getAllValues().get(1).getSortObject(), is(expectedSortObject));
}
@Test // DATAMONGO-1080
public void doesNotTryToPostProcessQueryResultIntoWrapperType() {
Person reference = new Person();
when(mongoOperationsMock.findOne(Mockito.any(Query.class), eq(Person.class), eq("persons"))).//
thenReturn(reference);
AbstractMongoQuery query = createQueryForMethod("findByLastname", String.class);
assertThat(query.execute(new Object[] { "lastname" }), is((Object) reference));
}
private MongoQueryFake createQueryForMethod(String methodName, Class<?>... paramTypes) {
try {
Method method = Repo.class.getMethod(methodName, paramTypes);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(Repo.class), factory,
mappingContextMock);
return new MongoQueryFake(queryMethod, mongoOperationsMock);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(e.getMessage(), e);
} catch (SecurityException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
private static class MongoQueryFake extends AbstractMongoQuery {
private boolean isCountQuery;
private boolean isExistsQuery;
private boolean isDeleteQuery;
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
super(method, operations);
}
@Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
return new BasicQuery("{'foo':'bar'}");
}
@Override
protected boolean isCountQuery() {
return isCountQuery;
}
@Override
protected boolean isExistsQuery() {
return false;
}
@Override
protected boolean isDeleteQuery() {
return isDeleteQuery;
}
public MongoQueryFake setDeleteQuery(boolean isDeleteQuery) {
this.isDeleteQuery = isDeleteQuery;
return this;
}
}
private interface Repo extends MongoRepository<Person, Long> {
List<Person> deleteByLastname(String lastname);
Long deletePersonByLastname(String lastname);
List<Person> findByFirstname(String firstname);
@Meta(comment = "comment", flags = {org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT})
Page<Person> findByFirstname(String firstnanme, Pageable pageable);
@Meta(comment = "comment")
@org.springframework.data.mongodb.repository.Query("{}")
Page<Person> findByAnnotatedQuery(String firstnanme, Pageable pageable);
// DATAMONGO-1057
Slice<Person> findByLastname(String lastname, Pageable page);
Optional<Person> findByLastname(String lastname);
}
}