package de.otto.edison.jobs.service;
import de.otto.edison.jobs.definition.JobDefinition;
import de.otto.edison.jobs.eventbus.JobEventPublisher;
import de.otto.edison.jobs.eventbus.JobEvents;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.net.URISyntaxException;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static de.otto.edison.jobs.definition.DefaultJobDefinition.fixedDelayJobDefinition;
import static de.otto.edison.jobs.definition.DefaultJobDefinition.manuallyTriggerableJobDefinition;
import static de.otto.edison.jobs.eventbus.events.StateChangeEvent.State.*;
import static de.otto.edison.jobs.service.JobRunner.PING_PERIOD;
import static de.otto.edison.jobs.service.JobRunner.newJobRunner;
import static java.time.Duration.ofSeconds;
import static java.util.Optional.empty;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
public class JobRunnerTest {
@Mock
private ScheduledExecutorService executor;
@Mock
private ScheduledFuture<?> scheduledJob;
@Mock
private JobEventPublisher jobEventPublisher;
private JobRunner jobRunner;
@Before
public void setUp() throws Exception {
initMocks(this);
jobRunner = newJobRunner(
"42",
"NAME",
executor,
jobEventPublisher
);
doReturn(scheduledJob)
.when(executor).scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class));
}
@Test
public void shouldExecuteJob() {
// given
JobRunnable jobRunnable = mock(JobRunnable.class);
when(jobRunnable.getJobDefinition()).thenReturn(fixedDelayJobDefinition("TYPE", "", "", ofSeconds(2), 0, empty()));
// when
jobRunner.start(jobRunnable);
// then
verify(jobRunnable).execute(jobEventPublisher);
}
@Test
public void shouldPublishErrorMessageWithStackTraceOnFail() throws URISyntaxException {
// given
JobRunnable jobRunnable = mock(JobRunnable.class);
when(jobRunnable.getJobDefinition()).thenReturn(fixedDelayJobDefinition("TYPE", "", "", ofSeconds(2), 0, empty()));
doThrow(new RuntimeException("some error")).when(jobRunnable).execute(jobEventPublisher);
// when
jobRunner.start(jobRunnable);
// then
verify(jobEventPublisher).error(startsWith("Fatal error in job NAME (42)\n" +
"java.lang.RuntimeException: some error\n"));
}
@Test
public void shouldRestartJobOnException() {
// given
JobRunnable jobRunnable = mock(JobRunnable.class);
when(jobRunnable.getJobDefinition())
.thenReturn(manuallyTriggerableJobDefinition("someJobType", "someJobname", "Me is testjob", 2, Optional.empty()));
doThrow(new RuntimeException("some error"))
.when(jobRunnable).execute(eq(jobEventPublisher));
// when
jobRunner.start(jobRunnable);
// then
verify(jobEventPublisher).stateChanged(START);
verify(jobRunnable, times(3)).execute(jobEventPublisher);
verify(jobEventPublisher, times(2)).stateChanged(RESTART);
verify(jobEventPublisher).stateChanged(STOP);
}
@Test
public void shouldSendKeepAliveEventWithinPingJob() {
// when
jobRunner.start(getMockedRunnable());
//then
ArgumentCaptor<Runnable> pingRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(executor).scheduleAtFixedRate(pingRunnableArgumentCaptor.capture(), eq(PING_PERIOD), eq(PING_PERIOD), eq(SECONDS));
pingRunnableArgumentCaptor.getValue().run();
verify(jobEventPublisher).stateChanged(KEEP_ALIVE);
}
@Test
public void shouldStopPingJobWhenJobIsFinished() {
// when
jobRunner.start(getMockedRunnable());
//then
verify(scheduledJob).cancel(false);
}
@Test
public void shouldInitJobEventsOnJobStart() {
jobRunner.start(new StubRunnable(()-> JobEvents.info("test")));
verify(jobEventPublisher).info("test");
}
@Test(expected = IllegalStateException.class)
public void shouldDestroyReferenceToJobEventPublisherWhenJobFinishes() {
jobRunner.start(getMockedRunnable());
JobEvents.info("I should produce an error");
}
private JobRunnable getMockedRunnable() {
final JobRunnable jobRunnable = mock(JobRunnable.class);
JobDefinition jobDefinition = mock(JobDefinition.class);
when(jobDefinition.jobType()).thenReturn("TYPE");
when(jobRunnable.getJobDefinition()).thenReturn(jobDefinition);
return jobRunnable;
}
private static class StubRunnable implements JobRunnable {
private final Runnable runnable;
StubRunnable(Runnable runnable) {
this.runnable = runnable;
}
@Override
public JobDefinition getJobDefinition() {
JobDefinition jobDefinition = mock(JobDefinition.class);
when(jobDefinition.jobType()).thenReturn("TYPE");
return jobDefinition;
}
@Override
public void execute(JobEventPublisher jobEventPublisher) {
runnable.run();
}
}
}