/* * Copyright 2013-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.repository.support; import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.data.repository.support.RepositoryInvocationTestUtils.*; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.Param; import org.springframework.data.repository.support.CrudRepositoryInvokerUnitTests.Person; import org.springframework.data.repository.support.CrudRepositoryInvokerUnitTests.PersonRepository; import org.springframework.data.repository.support.RepositoryInvocationTestUtils.VerifyingMethodInterceptor; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** * Integration tests for {@link ReflectionRepositoryInvoker}. * * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) public class ReflectionRepositoryInvokerUnitTests { static final Page<Person> EMPTY_PAGE = new PageImpl<>(Collections.emptyList()); ConversionService conversionService; @Before public void setUp() { this.conversionService = new DefaultFormattingConversionService(); } @Test // DATACMNS-589 public void invokesSaveMethodCorrectly() throws Exception { ManualCrudRepository repository = mock(ManualCrudRepository.class); Method method = ManualCrudRepository.class.getMethod("save", Domain.class); getInvokerFor(repository, expectInvocationOf(method)).invokeSave(new Domain()); } @Test // DATACMNS-589 public void invokesFindOneCorrectly() throws Exception { ManualCrudRepository repository = mock(ManualCrudRepository.class); Method method = ManualCrudRepository.class.getMethod("findById", Long.class); getInvokerFor(repository, expectInvocationOf(method)).invokeFindById("1"); getInvokerFor(repository, expectInvocationOf(method)).invokeFindById(1L); } @Test // DATACMNS-589 public void invokesDeleteWithDomainCorrectly() throws Exception { RepoWithDomainDeleteAndFindOne repository = mock(RepoWithDomainDeleteAndFindOne.class); when(repository.findById(1L)).thenReturn(new Domain()); Method findOneMethod = RepoWithDomainDeleteAndFindOne.class.getMethod("findById", Long.class); Method deleteMethod = RepoWithDomainDeleteAndFindOne.class.getMethod("delete", Domain.class); getInvokerFor(repository, expectInvocationOf(findOneMethod, deleteMethod)).invokeDeleteById(1L); } @Test // DATACMNS-589 public void invokesFindAllWithoutParameterCorrectly() throws Exception { Method method = ManualCrudRepository.class.getMethod("findAll"); ManualCrudRepository repository = mock(ManualCrudRepository.class); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged()); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(PageRequest.of(0, 10)); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.unsorted()); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.by("foo")); } @Test // DATACMNS-589 public void invokesFindAllWithSortCorrectly() throws Exception { Method method = RepoWithFindAllWithSort.class.getMethod("findAll", Sort.class); RepoWithFindAllWithSort repository = mock(RepoWithFindAllWithSort.class); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged()); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(PageRequest.of(0, 10)); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.unsorted()); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.by("foo")); } @Test // DATACMNS-589 public void invokesFindAllWithPageableCorrectly() throws Exception { Method method = RepoWithFindAllWithPageable.class.getMethod("findAll", Pageable.class); RepoWithFindAllWithPageable repository = mock(RepoWithFindAllWithPageable.class); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged()); getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(PageRequest.of(0, 10)); } @Test // DATACMNS-589 public void invokesQueryMethod() throws Exception { MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); parameters.add("firstName", "John"); Method method = PersonRepository.class.getMethod("findByFirstName", String.class, Pageable.class); PersonRepository repository = mock(PersonRepository.class); getInvokerFor(repository, expectInvocationOf(method)).invokeQueryMethod(method, parameters, Pageable.unpaged(), Sort.unsorted()); } @Test // DATACMNS-589 public void considersFormattingAnnotationsOnQueryMethodParameters() throws Exception { MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); parameters.add("date", "2013-07-18T10:49:00.000+02:00"); Method method = PersonRepository.class.getMethod("findByCreatedUsingISO8601Date", Date.class, Pageable.class); PersonRepository repository = mock(PersonRepository.class); getInvokerFor(repository, expectInvocationOf(method)).invokeQueryMethod(method, parameters, Pageable.unpaged(), Sort.unsorted()); } @Test // DATAREST-335, DATAREST-346, DATACMNS-589 public void invokesOverriddenDeleteMethodCorrectly() throws Exception { MyRepo repository = mock(MyRepo.class); Method method = CustomRepo.class.getMethod("deleteById", Long.class); getInvokerFor(repository, expectInvocationOf(method)).invokeDeleteById("1"); } @Test(expected = IllegalStateException.class) // DATACMNS-589 public void rejectsInvocationOfMissingDeleteMethod() { RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class)); assertThat(invoker.hasDeleteMethod()).isFalse(); invoker.invokeDeleteById(1L); } @Test(expected = IllegalStateException.class) // DATACMNS-589 public void rejectsInvocationOfMissingFindOneMethod() { RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class)); assertThat(invoker.hasFindOneMethod()).isFalse(); invoker.invokeFindById(1L); } @Test(expected = IllegalStateException.class) // DATACMNS-589 public void rejectsInvocationOfMissingFindAllMethod() { RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class)); assertThat(invoker.hasFindAllMethod()).isFalse(); invoker.invokeFindAll(Sort.unsorted()); } @Test(expected = IllegalStateException.class) // DATACMNS-589 public void rejectsInvocationOfMissingSaveMethod() { RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class)); assertThat(invoker.hasSaveMethod()).isFalse(); invoker.invokeSave(new Object()); } @Test // DATACMNS-647 public void translatesCollectionRequestParametersCorrectly() throws Exception { for (String[] ids : Arrays.asList(new String[] { "1,2" }, new String[] { "1", "2" })) { MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); parameters.put("ids", Arrays.asList(ids)); Method method = PersonRepository.class.getMethod("findByIdIn", Collection.class); PersonRepository repository = mock(PersonRepository.class); getInvokerFor(repository, expectInvocationOf(method)).invokeQueryMethod(method, parameters, Pageable.unpaged(), Sort.unsorted()); } } @Test // DATACMNS-700 public void failedParameterConversionCapturesContext() throws Exception { RepositoryInvoker invoker = getInvokerFor(mock(SimpleRepository.class)); MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>(); parameters.add("value", "value"); Method method = SimpleRepository.class.getMethod("findByClass", int.class); try { invoker.invokeQueryMethod(method, parameters, Pageable.unpaged(), Sort.unsorted()); } catch (QueryMethodParameterConversionException o_O) { assertThat(o_O.getParameter()).isEqualTo(new MethodParameters(method).getParameters().get(0)); assertThat(o_O.getSource()).isEqualTo("value"); assertThat(o_O.getCause()).isInstanceOf(ConversionFailedException.class); } } @Test // DATACMNS-867 public void convertsWrapperTypeToJdkOptional() { GuavaRepository mock = mock(GuavaRepository.class); when(mock.findById(any())).thenReturn(com.google.common.base.Optional.of(new Domain())); RepositoryInvoker invoker = getInvokerFor(mock); Optional<Object> invokeFindOne = invoker.invokeFindById(1L); assertThat(invokeFindOne).isPresent(); } @Test // DATACMNS-867 public void wrapsSingleElementCollectionIntoOptional() throws Exception { ManualCrudRepository mock = mock(ManualCrudRepository.class); when(mock.findAll()).thenReturn(Arrays.asList(new Domain())); Method method = ManualCrudRepository.class.getMethod("findAll"); Optional<Object> result = getInvokerFor(mock).invokeQueryMethod(method, new LinkedMultiValueMap<>(), Pageable.unpaged(), Sort.unsorted()); assertThat(result).hasValueSatisfying(it -> { assertThat(it).isInstanceOf(Collection.class); }); } private static RepositoryInvoker getInvokerFor(Object repository) { RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]); GenericConversionService conversionService = new DefaultFormattingConversionService(); return new ReflectionRepositoryInvoker(repository, metadata, conversionService); } private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) { return getInvokerFor(getVerifyingRepositoryProxy(repository, interceptor)); } interface MyRepo extends CustomRepo, CrudRepository<Domain, Long> {} class Domain {} interface CustomRepo { void deleteById(Long id); } interface EmptyRepository extends Repository<Domain, Long> {} interface ManualCrudRepository extends Repository<Domain, Long> { Domain findById(Long id); Iterable<Domain> findAll(); <T extends Domain> T save(T entity); void deleteById(Long id); } interface RepoWithFindAllWithoutParameters extends Repository<Domain, Long> { List<Domain> findAll(); } interface RepoWithFindAllWithPageable extends Repository<Domain, Long> { Page<Domain> findAll(Pageable pageable); } interface RepoWithFindAllWithSort extends Repository<Domain, Long> { Page<Domain> findAll(Sort sort); } interface RepoWithDomainDeleteAndFindOne extends Repository<Domain, Long> { Domain findById(Long id); void delete(Domain entity); } interface SimpleRepository extends Repository<Domain, Long> { Domain findByClass(@Param("value") int value); } interface GuavaRepository extends Repository<Domain, Long> { com.google.common.base.Optional<Domain> findById(Long id); } }