package de.otto.edison.jobs.service;

import de.otto.edison.jobs.definition.JobDefinition;
import de.otto.edison.jobs.eventbus.JobEventPublisher;
import org.mockito.ArgumentCaptor;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

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.*;

public class JobRunnerTest {

    private ScheduledExecutorService executor;
    private ScheduledFuture scheduledJob;
    private JobEventPublisher jobEventPublisher;

    @BeforeMethod
    public void setUp() throws Exception {
        executor = mock(ScheduledExecutorService.class);
        jobEventPublisher = mock(JobEventPublisher.class);

        scheduledJob = mock(ScheduledFuture.class);
        doReturn(scheduledJob)
                .when(executor).scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class));
    }

    @Test
    public void shouldExecuteJob() {
        // given
        JobRunner jobRunner = newJobRunner("42", "TYPE", executor, jobEventPublisher);
        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
        JobRunner jobRunner = newJobRunner(
                "42",
                "NAME",
                executor,
                jobEventPublisher
        );
        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
        JobRunner jobRunner = newJobRunner(
                "42",
                "NAME",
                executor,
                jobEventPublisher
        );

        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() {
        //given
        JobRunner jobRunner = newJobRunner(
                "42",
                "NAME",
                executor,
                jobEventPublisher
        );

        // 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() {
        //given
        JobRunner jobRunner = newJobRunner(
                "42",
                "NAME",
                executor,
                jobEventPublisher
        );

        // when
        jobRunner.start(getMockedRunnable());

        //then
        verify(scheduledJob).cancel(false);
    }

    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;
    }
}

@@ -174,16 +174,41 @@
 Captor;%0A
+import org.mockito.Mock;%0A
 import o
@@ -1092,16 +1092,72 @@
 ckito.*;
+%0Aimport static org.mockito.MockitoAnnotations.initMocks;
 %0A%0Apublic
@@ -1180,16 +1180,26 @@
 Test %7B%0A%0A
+    @Mock%0A
     priv
@@ -1237,16 +1237,26 @@
 ecutor;%0A
+    @Mock%0A
     priv
@@ -1289,16 +1289,26 @@
 ledJob;%0A
+    @Mock%0A
     priv
@@ -1347,16 +1347,49 @@
 blisher;
+%0A    private JobRunner jobRunner;
 %0A%0A    @B
@@ -1455,57 +1455,26 @@
     
-executor = mock(ScheduledExecutorService.clas
+initMocks(thi
 s);%0A
+%0A
     
@@ -1484,109 +1484,149 @@
  job
-EventPublisher = mock(JobEventPublisher.class);%0A%0A        scheduledJob = mock(ScheduledFuture.class
+Runner = newJobRunner(%0A                %2242%22,%0A                %22NAME%22,%0A                executor,%0A                jobEventPublisher%0A        
 );%0A
+%0A
     
@@ -1844,95 +1844,8 @@
 ven%0A
-        JobRunner jobRunner = newJobRunner(%2242%22, %22TYPE%22, executor, jobEventPublisher);%0A
     
@@ -2281,169 +2281,8 @@
 ven%0A
-        JobRunner jobRunner = newJobRunner(%0A                %2242%22,%0A                %22NAME%22,%0A                executor,%0A                jobEventPublisher%0A        );%0A
     
@@ -2864,170 +2864,8 @@
 ven%0A
-        JobRunner jobRunner = newJobRunner(%0A                %2242%22,%0A                %22NAME%22,%0A                executor,%0A                jobEventPublisher%0A        );%0A%0A
     
@@ -3608,186 +3608,8 @@
 ) %7B%0A
-        //given%0A        JobRunner jobRunner = newJobRunner(%0A                %2242%22,%0A                %22NAME%22,%0A                executor,%0A                jobEventPublisher%0A        );%0A%0A
     
@@ -4107,186 +4107,8 @@
 ) %7B%0A
-        //given%0A        JobRunner jobRunner = newJobRunner(%0A                %2242%22,%0A                %22NAME%22,%0A                executor,%0A                jobEventPublisher%0A        );%0A%0A
     

Clean JobRunnerTest