/*
* Copyright 2016-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.core.support;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import lombok.Getter;
import lombok.Value;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.DomainEvents;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.EventPublishingRepositoryProxyPostProcessor.EventPublishingMethod;
import org.springframework.data.repository.core.support.EventPublishingRepositoryProxyPostProcessor.EventPublishingMethodInterceptor;
/**
* Unit tests for {@link EventPublishingRepositoryProxyPostProcessor} and contained classes.
*
* @author Oliver Gierke
* @author Mark Paluch
* @soundtrack Henrik Freischlader Trio - Nobody Else To Blame (Openness)
*/
@RunWith(MockitoJUnitRunner.class)
public class EventPublishingRepositoryProxyPostProcessorUnitTests {
@Mock ApplicationEventPublisher publisher;
@Mock MethodInvocation invocation;
@Test(expected = IllegalArgumentException.class) // DATACMNS-928
public void rejectsNullAggregateTypes() {
EventPublishingMethod.of(null);
}
@Test // DATACMNS-928
public void publishingEventsForNullIsNoOp() {
EventPublishingMethod.of(OneEvent.class).publishEventsFrom(null, publisher);
}
@Test // DATACMNS-928
public void exposesEventsExposedByEntityToPublisher() {
SomeEvent first = new SomeEvent();
SomeEvent second = new SomeEvent();
MultipleEvents entity = MultipleEvents.of(Arrays.asList(first, second));
EventPublishingMethod.of(MultipleEvents.class).publishEventsFrom(entity, publisher);
verify(publisher).publishEvent(eq(first));
verify(publisher).publishEvent(eq(second));
}
@Test // DATACMNS-928
public void exposesSingleEventByEntityToPublisher() {
SomeEvent event = new SomeEvent();
OneEvent entity = OneEvent.of(event);
EventPublishingMethod.of(OneEvent.class).publishEventsFrom(entity, publisher);
verify(publisher, times(1)).publishEvent(event);
}
@Test // DATACMNS-928
public void doesNotExposeNullEvent() {
OneEvent entity = OneEvent.of(null);
EventPublishingMethod.of(OneEvent.class).publishEventsFrom(entity, publisher);
verify(publisher, times(0)).publishEvent(any());
}
@Test // DATACMNS-928
public void doesNotCreatePublishingMethodIfNoAnnotationDetected() {
assertThat(EventPublishingMethod.of(Object.class)).isNull();
}
@Test // DATACMNS-928
public void interceptsSaveMethod() throws Throwable {
doReturn(SampleRepository.class.getMethod("save", Object.class)).when(invocation).getMethod();
SomeEvent event = new SomeEvent();
MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event));
doReturn(new Object[] { sample }).when(invocation).getArguments();
EventPublishingMethodInterceptor//
.of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
.invoke(invocation);
verify(publisher).publishEvent(event);
}
@Test // DATACMNS-928
public void doesNotInterceptNonSaveMethod() throws Throwable {
doReturn(SampleRepository.class.getMethod("findById", Object.class)).when(invocation).getMethod();
EventPublishingMethodInterceptor//
.of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
.invoke(invocation);
verify(publisher, never()).publishEvent(any());
}
@Test // DATACMNS-928
public void registersAdviceIfDomainTypeExposesEvents() {
RepositoryInformation information = new DummyRepositoryInformation(SampleRepository.class);
RepositoryProxyPostProcessor processor = new EventPublishingRepositoryProxyPostProcessor(publisher);
ProxyFactory factory = mock(ProxyFactory.class);
processor.postProcess(factory, information);
verify(factory).addAdvice(any(EventPublishingMethodInterceptor.class));
}
@Test // DATACMNS-928
public void doesNotAddAdviceIfDomainTypeDoesNotExposeEvents() {
RepositoryInformation information = new DummyRepositoryInformation(CrudRepository.class);
RepositoryProxyPostProcessor processor = new EventPublishingRepositoryProxyPostProcessor(publisher);
ProxyFactory factory = mock(ProxyFactory.class);
processor.postProcess(factory, information);
verify(factory, never()).addAdvice(any(Advice.class));
}
@Test // DATACMNS-928
public void publishesEventsForCallToSaveWithIterable() throws Throwable {
SomeEvent event = new SomeEvent();
MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event));
doReturn(new Object[] { Collections.singletonList(sample) }).when(invocation).getArguments();
doReturn(SampleRepository.class.getMethod("saveAll", Iterable.class)).when(invocation).getMethod();
EventPublishingMethodInterceptor//
.of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
.invoke(invocation);
verify(publisher).publishEvent(any(SomeEvent.class));
}
@Test // DATACMNS-975
public void publishesEventsAfterSaveInvocation() throws Throwable {
doThrow(new IllegalStateException()).when(invocation).proceed();
try {
EventPublishingMethodInterceptor//
.of(EventPublishingMethod.of(OneEvent.class), publisher)//
.invoke(invocation);
} catch (IllegalStateException o_O) {
verify(publisher, never()).publishEvent(any(SomeEvent.class));
}
}
@Test // DATACMNS-1113
public void invokesEventsForMethodsThatStartsWithSave() throws Throwable {
Method method = SampleRepository.class.getMethod("saveAndFlush", MultipleEvents.class);
doReturn(method).when(invocation).getMethod();
SomeEvent event = new SomeEvent();
MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event));
doReturn(new Object[] { sample }).when(invocation).getArguments();
EventPublishingMethodInterceptor//
.of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
.invoke(invocation);
verify(publisher).publishEvent(event);
}
@Value(staticConstructor = "of")
static class MultipleEvents {
@Getter(onMethod = @__(@DomainEvents)) Collection<? extends Object> events;
}
@Value(staticConstructor = "of")
static class OneEvent {
@Getter(onMethod = @__(@DomainEvents)) Object event;
}
@Value
static class SomeEvent {
UUID id = UUID.randomUUID();
}
interface SampleRepository extends CrudRepository<MultipleEvents, Long> {
MultipleEvents saveAndFlush(MultipleEvents events);
}
}