/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.ambari.server.scheduler;

import com.google.gson.Gson;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.persist.PersistService;
import com.google.inject.persist.Transactional;
import com.google.inject.util.Modules;
import junit.framework.Assert;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.ActionDBAccessor;
import org.apache.ambari.server.actionmanager.HostRoleCommand;
import org.apache.ambari.server.actionmanager.HostRoleStatus;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.orm.GuiceJpaInitializer;
import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
import org.apache.ambari.server.security.authorization.internal.InternalTokenStorage;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.server.state.scheduler.*;
import org.easymock.Capture;
import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import static org.easymock.EasyMock.*;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;

public class ExecutionScheduleManagerTest {
  private Clusters clusters;
  private Cluster cluster;
  private String clusterName;
  private Injector injector;
  private AmbariMetaInfo metaInfo;
  private ExecutionScheduleManager executionScheduleManager;
  private RequestExecutionFactory requestExecutionFactory;
  private ExecutionScheduler executionScheduler;
  private Scheduler scheduler;
  Properties properties;

  private static final Logger LOG =
    LoggerFactory.getLogger(ExecutionScheduleManagerTest.class);

  @Before
  public void setup() throws Exception {
    InMemoryDefaultTestModule defaultTestModule = new InMemoryDefaultTestModule();
    properties = defaultTestModule.getProperties();
    injector  = Guice.createInjector(Modules.override(defaultTestModule)
      .with(new ExecutionSchedulerTestModule()));
    injector.getInstance(GuiceJpaInitializer.class);
    clusters = injector.getInstance(Clusters.class);
    metaInfo = injector.getInstance(AmbariMetaInfo.class);
    executionScheduleManager = injector.getInstance(ExecutionScheduleManager.class);
    executionScheduler = injector.getInstance(ExecutionScheduler.class);
    requestExecutionFactory = injector.getInstance(RequestExecutionFactory.class);

    metaInfo.init();
    clusterName = "c1";
    clusters.addCluster(clusterName);
    cluster = clusters.getCluster(clusterName);
    cluster.setDesiredStackVersion(new StackId("HDP-0.1"));
    Assert.assertNotNull(cluster);
    assertThat(executionScheduler, instanceOf(TestExecutionScheduler.class));

    TestExecutionScheduler testExecutionScheduler = (TestExecutionScheduler)
      executionScheduler;
    scheduler = testExecutionScheduler.getScheduler();
    Assert.assertNotNull(scheduler);

    executionScheduleManager.start();
  }

  @After
  public void teardown() throws Exception {
    executionScheduleManager.stop();
    injector.getInstance(PersistService.class).stop();
  }

  public static class TestExecutionScheduler extends ExecutionSchedulerImpl {
    @Inject
    public TestExecutionScheduler(Injector injector) {
      super(injector);
      try {
        StdSchedulerFactory factory = new StdSchedulerFactory();
        scheduler = factory.getScheduler();
        isInitialized = true;
      } catch (SchedulerException e) {
        e.printStackTrace();
        throw new ExceptionInInitializerError("Unable to instantiate " +
          "scheduler");
      }
    }

    public Scheduler getScheduler() {
      return scheduler;
    }
  }

  public class ExecutionSchedulerTestModule implements Module {
    @Override
    public void configure(Binder binder) {
      binder.bind(ExecutionScheduler.class).to(TestExecutionScheduler.class);
    }
  }

  @Transactional
  private RequestExecution createRequestExecution(boolean addSchedule)
      throws Exception {
    Batch batches = new Batch();
    Schedule schedule = new Schedule();

    BatchSettings batchSettings = new BatchSettings();
    batchSettings.setTaskFailureToleranceLimit(10);
    batches.setBatchSettings(batchSettings);

    List<BatchRequest> batchRequests = new ArrayList<BatchRequest>();
    BatchRequest batchRequest1 = new BatchRequest();
    batchRequest1.setOrderId(10L);
    batchRequest1.setType(BatchRequest.Type.DELETE);
    batchRequest1.setUri("testUri1");

    BatchRequest batchRequest2 = new BatchRequest();
    batchRequest2.setOrderId(12L);
    batchRequest2.setType(BatchRequest.Type.POST);
    batchRequest2.setUri("testUri2");
    batchRequest2.setBody("testBody");

    batchRequests.add(batchRequest1);
    batchRequests.add(batchRequest2);

    batches.getBatchRequests().addAll(batchRequests);

    schedule.setMinutes("10");
    schedule.setHours("2");
    schedule.setMonth("*");
    schedule.setDaysOfMonth("*");
    schedule.setDayOfWeek("?");

    if (!addSchedule) {
      schedule = null;
    }

    RequestExecution requestExecution = requestExecutionFactory.createNew
      (cluster, batches, schedule);
    requestExecution.setDescription("Test Schedule");

    requestExecution.persist();

    return requestExecution;
  }

  @Test
  public void testScheduleBatch() throws Exception {
    RequestExecution requestExecution = createRequestExecution(true);
    Assert.assertNotNull(requestExecution);

    executionScheduleManager.scheduleBatch(requestExecution);

    String jobName1 = executionScheduleManager.getJobName(requestExecution
      .getId(), 10L);
    String jobName2 = executionScheduleManager.getJobName(requestExecution
      .getId(), 12L);
    JobDetail jobDetail1 = null;
    JobDetail jobDetail2 = null;
    Trigger trigger1 = null;
    Trigger trigger2 = null;

    // enumerate each job group
    for(String group: scheduler.getJobGroupNames()) {
      // enumerate each job in group
      for(JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals
          (ExecutionJob.LINEAR_EXECUTION_JOB_GROUP))) {
        LOG.info("Found job identified by: " + jobKey);

        String jobName = jobKey.getName();
        String jobGroup = jobKey.getGroup();

        List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jobKey);
        Trigger trigger = triggers != null && !triggers.isEmpty() ?
          triggers.get(0) : null;
        Date nextFireTime = trigger != null ? trigger.getNextFireTime() : null;

        LOG.info("[jobName] : " + jobName + " [groupName] : "
          + jobGroup + " - " + nextFireTime);

        if (jobName.equals(jobName1)) {
          jobDetail1 = scheduler.getJobDetail(jobKey);
          trigger1 = trigger;
        } else if (jobName.equals(jobName2)) {
          jobDetail2 = scheduler.getJobDetail(jobKey);
          trigger2 = trigger;
        }
      }
    }

    Assert.assertNotNull(jobDetail1);
    Assert.assertNotNull(trigger1);
    Assert.assertNotNull(jobDetail2);
    Assert.assertNull(trigger2);

    CronTrigger cronTrigger = (CronTrigger) trigger1;
    Schedule schedule = new Schedule();
    schedule.setMinutes("10");
    schedule.setHours("2");
    schedule.setMonth("*");
    schedule.setDaysOfMonth("*");
    schedule.setDayOfWeek("?");

    Assert.assertEquals(schedule.getScheduleExpression(),
      cronTrigger.getCronExpression());

    Assert.assertEquals(jobName1, jobDetail1.getKey().getName());
    Assert.assertEquals(jobName2, jobDetail2.getKey().getName());
  }

  @Test
  public void testDeleteAllJobs() throws Exception {
    RequestExecution requestExecution = createRequestExecution(true);
    Assert.assertNotNull(requestExecution);

    executionScheduleManager.scheduleBatch(requestExecution);

    String jobName1 = executionScheduleManager.getJobName(requestExecution
      .getId(), 10L);
    String jobName2 = executionScheduleManager.getJobName(requestExecution
      .getId(), 12L);

    JobDetail jobDetail1 = scheduler.getJobDetail(JobKey.jobKey(jobName1,
      ExecutionJob.LINEAR_EXECUTION_JOB_GROUP));
    JobDetail jobDetail2 = scheduler.getJobDetail(JobKey.jobKey(jobName2,
      ExecutionJob.LINEAR_EXECUTION_JOB_GROUP));

    Assert.assertNotNull(jobDetail1);
    Assert.assertNotNull(jobDetail2);
    Assert.assertTrue(!scheduler.getTriggersOfJob(JobKey.jobKey(jobName1,
      ExecutionJob.LINEAR_EXECUTION_JOB_GROUP)).isEmpty());

    executionScheduleManager.deleteAllJobs(requestExecution);

    Assert.assertTrue(scheduler.getTriggersOfJob(JobKey.jobKey(jobName1,
      ExecutionJob.LINEAR_EXECUTION_JOB_GROUP)).isEmpty());
  }

  @Test
  public void testPointInTimeExecutionJob() throws Exception {
    RequestExecution requestExecution = createRequestExecution(false);
    Assert.assertNotNull(requestExecution);

    executionScheduleManager.scheduleBatch(requestExecution);

    String jobName1 = executionScheduleManager.getJobName(requestExecution
      .getId(), 10L);
    String jobName2 = executionScheduleManager.getJobName(requestExecution
      .getId(), 12L);

    JobDetail jobDetail1 = scheduler.getJobDetail(JobKey.jobKey(jobName1,
      ExecutionJob.LINEAR_EXECUTION_JOB_GROUP));
    JobDetail jobDetail2 = scheduler.getJobDetail(JobKey.jobKey(jobName2,
      ExecutionJob.LINEAR_EXECUTION_JOB_GROUP));

    Assert.assertNotNull(jobDetail1);
    Assert.assertNotNull(jobDetail2);

    List<? extends Trigger> triggers = scheduler.getTriggersOfJob
      (JobKey.jobKey(jobName1, ExecutionJob.LINEAR_EXECUTION_JOB_GROUP));

    Assert.assertNotNull(triggers);
    Assert.assertEquals(1, triggers.size());
    assertThat(triggers.get(0), instanceOf(SimpleTrigger.class));

    Assert.assertNull(jobDetail2.getJobDataMap().getString(
      ExecutionJob.NEXT_EXECUTION_JOB_NAME_KEY));

    int waitCount = 0;
    while (scheduler.getCurrentlyExecutingJobs().size() != 0 && waitCount < 10) {
      Thread.sleep(100);
      waitCount++;
    }
  }

  @Test
  public void testExecuteBatchRequest() throws Exception {
    Clusters clustersMock = createMock(Clusters.class);
    Cluster clusterMock = createMock(Cluster.class);
    RequestExecution requestExecutionMock = createMock(RequestExecution.class);
    Configuration configurationMock = createNiceMock(Configuration.class);
    ExecutionScheduler executionSchedulerMock = createMock(ExecutionScheduler.class);
    InternalTokenStorage tokenStorageMock = createMock(InternalTokenStorage.class);
    ActionDBAccessor actionDBAccessorMock = createMock(ActionDBAccessor.class);
    Gson gson = new Gson();
    BatchRequest batchRequestMock = createMock(BatchRequest.class);

    long executionId = 11L;
    long batchId = 1L;
    long requestId = 5L;
    String clusterName = "mycluster";
    String uri = "clusters";
    String type = "post";
    String body = "body";
    Map<Long, RequestExecution> executionMap = new HashMap<Long, RequestExecution>();
    executionMap.put(executionId, requestExecutionMock);

    BatchRequestResponse batchRequestResponse = new BatchRequestResponse();
    batchRequestResponse.setStatus(HostRoleStatus.IN_PROGRESS.toString());
    batchRequestResponse.setRequestId(requestId);
    batchRequestResponse.setReturnCode(202);

    ExecutionScheduleManager scheduleManager = createMockBuilder(ExecutionScheduleManager.class).
      withConstructor(configurationMock, executionSchedulerMock, tokenStorageMock, clustersMock,
        actionDBAccessorMock, gson).
      addMockedMethods("performApiRequest", "updateBatchRequest").createNiceMock();

    //interesting easymock behavior, workaround to not to expect method called in constructor
    expectLastCall().anyTimes();

    expect(clustersMock.getCluster(clusterName)).andReturn(clusterMock).anyTimes();
    expect(clusterMock.getAllRequestExecutions()).andReturn(executionMap).anyTimes();


    expect(requestExecutionMock.getBatchRequest(eq(batchId))).andReturn(batchRequestMock).once();
    expect(requestExecutionMock.getRequestBody(eq(batchId))).andReturn(body).once();

    expect(batchRequestMock.getUri()).andReturn(uri).once();
    expect(batchRequestMock.getType()).andReturn(type).once();

    expect(scheduleManager.performApiRequest(eq(uri), eq(body), eq(type))).andReturn(batchRequestResponse).once();

    scheduleManager.updateBatchRequest(eq(executionId), eq(batchId), eq(clusterName), eq(batchRequestResponse), eq(false));
    expectLastCall().once();

    actionDBAccessorMock.setSourceScheduleForRequest(eq(requestId), eq(executionId));
    expectLastCall().once();

    replay(clusterMock, clustersMock, configurationMock, requestExecutionMock, executionSchedulerMock,
      tokenStorageMock, batchRequestMock, scheduleManager, actionDBAccessorMock);

    scheduleManager.executeBatchRequest(executionId, batchId, clusterName);

    verify(clusterMock, clustersMock, configurationMock, requestExecutionMock, executionSchedulerMock,
      tokenStorageMock, batchRequestMock, scheduleManager, actionDBAccessorMock);

  }

  @Test
  public void testUpdateBatchRequest() throws Exception {
    Clusters clustersMock = createMock(Clusters.class);
    Cluster clusterMock = createMock(Cluster.class);
    RequestExecution requestExecutionMock = createMock(RequestExecution.class);
    Configuration configurationMock = createNiceMock(Configuration.class);
    ExecutionScheduler executionSchedulerMock = createMock(ExecutionScheduler.class);
    InternalTokenStorage tokenStorageMock = createMock(InternalTokenStorage.class);
    ActionDBAccessor actionDBAccessorMock = createMock(ActionDBAccessor.class);
    Gson gson = new Gson();
    BatchRequest batchRequestMock = createMock(BatchRequest.class);

    long executionId = 11L;
    long batchId = 1L;
    long requestId = 5L;
    String clusterName = "mycluster";

    Map<Long, RequestExecution> executionMap = new HashMap<Long, RequestExecution>();
    executionMap.put(executionId, requestExecutionMock);

    BatchRequestResponse batchRequestResponse = new BatchRequestResponse();
    batchRequestResponse.setStatus(HostRoleStatus.IN_PROGRESS.toString());
    batchRequestResponse.setRequestId(requestId);
    batchRequestResponse.setReturnCode(202);

    ExecutionScheduleManager scheduleManager = createMockBuilder(ExecutionScheduleManager.class).
      withConstructor(configurationMock, executionSchedulerMock, tokenStorageMock, clustersMock,
        actionDBAccessorMock, gson).
      addMockedMethods("performApiRequest").createNiceMock();

    //interesting easymock behavior, workaround to not to expect method called in constructor
    expectLastCall().anyTimes();

    expect(clustersMock.getCluster(clusterName)).andReturn(clusterMock).anyTimes();
    expect(clusterMock.getAllRequestExecutions()).andReturn(executionMap).anyTimes();

    requestExecutionMock.updateBatchRequest(eq(batchId), eq(batchRequestResponse), eq(true));
    expectLastCall().once();


    replay(clusterMock, clustersMock, configurationMock, requestExecutionMock, executionSchedulerMock,
      tokenStorageMock, batchRequestMock, scheduleManager);

    scheduleManager.updateBatchRequest(executionId, batchId, clusterName, batchRequestResponse, true);

    verify(clusterMock, clustersMock, configurationMock, requestExecutionMock, executionSchedulerMock,
      tokenStorageMock, batchRequestMock, scheduleManager);

  }

  @Test
  public void testGetBatchRequestResponse() throws Exception {
    Clusters clustersMock = createMock(Clusters.class);
    Cluster clusterMock = createMock(Cluster.class);
    Configuration configurationMock = createNiceMock(Configuration.class);
    ExecutionScheduler executionSchedulerMock = createMock(ExecutionScheduler.class);
    InternalTokenStorage tokenStorageMock = createMock(InternalTokenStorage.class);
    ActionDBAccessor actionDBAccessorMock = createMock(ActionDBAccessor.class);
    Gson gson = new Gson();

    long requestId = 5L;
    String clusterName = "mycluster";
    String apiUri = "api/v1/clusters/mycluster/requests/5";
    Capture<String> uriCapture= new Capture<String>();

    BatchRequestResponse batchRequestResponse = new BatchRequestResponse();
    batchRequestResponse.setStatus(HostRoleStatus.IN_PROGRESS.toString());
    batchRequestResponse.setRequestId(requestId);
    batchRequestResponse.setReturnCode(202);

    ExecutionScheduleManager scheduleManager = createMockBuilder(ExecutionScheduleManager.class).
      withConstructor(configurationMock, executionSchedulerMock, tokenStorageMock, clustersMock,
        actionDBAccessorMock, gson).
      addMockedMethods("performApiGetRequest").createNiceMock();

    //interesting easymock behavior, workaround to not to expect method called in constructor
    expectLastCall().anyTimes();


    expect(scheduleManager.performApiGetRequest(capture(uriCapture), eq(true))).andReturn(batchRequestResponse).once();

    replay(clusterMock, clustersMock, configurationMock, executionSchedulerMock,
      tokenStorageMock, scheduleManager);

    scheduleManager.getBatchRequestResponse(requestId, clusterName);

    verify(clusterMock, clustersMock, configurationMock, executionSchedulerMock,
      tokenStorageMock, scheduleManager);

    assertEquals(apiUri, uriCapture.getValue());
  }

  @Test
  public void testHasToleranceThresholdExceeded() throws Exception {
    Clusters clustersMock = createMock(Clusters.class);
    Cluster clusterMock = createMock(Cluster.class);
    Configuration configurationMock = createNiceMock(Configuration.class);
    ExecutionScheduler executionSchedulerMock = createMock(ExecutionScheduler.class);
    InternalTokenStorage tokenStorageMock = createMock(InternalTokenStorage.class);
    ActionDBAccessor actionDBAccessorMock = createMock(ActionDBAccessor.class);
    Gson gson = new Gson();
    RequestExecution requestExecutionMock = createMock(RequestExecution.class);
    Batch batchMock = createMock(Batch.class);

    long executionId = 11L;
    String clusterName = "c1";

    BatchSettings batchSettings = new BatchSettings();
    batchSettings.setTaskFailureToleranceLimit(1);

    Map<Long, RequestExecution> executionMap = new HashMap<Long, RequestExecution>();
    executionMap.put(executionId, requestExecutionMock);

    expect(clustersMock.getCluster(clusterName)).andReturn(clusterMock).anyTimes();
    expect(clusterMock.getAllRequestExecutions()).andReturn(executionMap).anyTimes();
    expect(requestExecutionMock.getBatch()).andReturn(batchMock).anyTimes();
    expect(batchMock.getBatchSettings()).andReturn(batchSettings).anyTimes();

    replay(clustersMock, clusterMock, configurationMock, requestExecutionMock,
      executionSchedulerMock, batchMock);

    ExecutionScheduleManager scheduleManager =
      new ExecutionScheduleManager(configurationMock, executionSchedulerMock,
        tokenStorageMock, clustersMock, actionDBAccessorMock, gson);

    HashMap<String, Integer> taskCounts = new HashMap<String, Integer>() {{
      put(BatchRequestJob.BATCH_REQUEST_FAILED_TASKS_KEY, 2);
      put(BatchRequestJob.BATCH_REQUEST_TOTAL_TASKS_KEY, 10);
    }};

    boolean exceeded = scheduleManager.hasToleranceThresholdExceeded
      (executionId, clusterName, taskCounts);

    Assert.assertTrue(exceeded);

    verify(clustersMock, clusterMock, configurationMock, requestExecutionMock,
      executionSchedulerMock, batchMock);
  }

  @SuppressWarnings("unchecked")
  @Test
  public void testFinalizeBatch() throws Exception {
    Clusters clustersMock = createMock(Clusters.class);
    Cluster clusterMock = createMock(Cluster.class);
    Configuration configurationMock = createNiceMock(Configuration.class);
    ExecutionScheduler executionSchedulerMock = createMock(ExecutionScheduler.class);
    InternalTokenStorage tokenStorageMock = createMock(InternalTokenStorage.class);
    ActionDBAccessor actionDBAccessorMock = createMock(ActionDBAccessor.class);
    Gson gson = new Gson();
    RequestExecution requestExecutionMock = createMock(RequestExecution.class);
    Batch batchMock = createMock(Batch.class);
    JobDetail jobDetailMock = createMock(JobDetail.class);
    final BatchRequest batchRequestMock = createMock(BatchRequest.class);
    final Trigger triggerMock = createNiceMock(Trigger.class);
    final List<Trigger> triggers = new ArrayList<Trigger>()  {{ add(triggerMock); }};

    long executionId = 11L;
    String clusterName = "c1";
    Date pastDate = new Date(new Date().getTime() - 2);

    Map<Long, RequestExecution> executionMap = new HashMap<Long, RequestExecution>();
    executionMap.put(executionId, requestExecutionMock);

    ExecutionScheduleManager scheduleManager =
      createMockBuilder(ExecutionScheduleManager.class).withConstructor
        (configurationMock, executionSchedulerMock, tokenStorageMock,
          clustersMock, actionDBAccessorMock, gson).createMock();

    expectLastCall().anyTimes();

    expect(clustersMock.getCluster(clusterName)).andReturn(clusterMock).anyTimes();
    expect(clusterMock.getAllRequestExecutions()).andReturn(executionMap).anyTimes();
    expect(requestExecutionMock.getBatch()).andReturn(batchMock).anyTimes();
    expect(batchMock.getBatchRequests()).andReturn
      (new ArrayList<BatchRequest>() {{
        add(batchRequestMock);
      }});
    expect(batchRequestMock.getOrderId()).andReturn(1L).anyTimes();
    expect(executionSchedulerMock.getJobDetail((JobKey) anyObject()))
      .andReturn(jobDetailMock).anyTimes();
    expect((List<Trigger>) executionSchedulerMock
      .getTriggersForJob((JobKey) anyObject())).andReturn(triggers).anyTimes();
    expect(triggerMock.mayFireAgain()).andReturn(true).anyTimes();
    expect(triggerMock.getFinalFireTime()).andReturn(pastDate).anyTimes();

    requestExecutionMock.updateStatus(RequestExecution.Status.COMPLETED);
    expectLastCall();

    replay(clustersMock, clusterMock, configurationMock, requestExecutionMock,
      executionSchedulerMock, scheduleManager, batchMock, batchRequestMock,
      triggerMock, jobDetailMock, actionDBAccessorMock);

    scheduleManager.finalizeBatch(executionId, clusterName);

    verify(clustersMock, clusterMock, configurationMock, requestExecutionMock,
      executionSchedulerMock, scheduleManager, batchMock, batchRequestMock,
      triggerMock, jobDetailMock, actionDBAccessorMock);
  }

  @Test
  public void testFinalizeBeforeExit() throws Exception {
    ExecutionScheduleManager scheduleManagerMock = createMock(ExecutionScheduleManager.class);
    AbstractLinearExecutionJob executionJob =
      createMockBuilder(AbstractLinearExecutionJob.class)
      .addMockedMethods("finalizeExecution", "doWork")
      .withConstructor(scheduleManagerMock)
      .createMock();
    JobExecutionContext context = createMock(JobExecutionContext.class);
    JobDetail jobDetail = createMock(JobDetail.class);
    JobDataMap jobDataMap = createMock(JobDataMap.class);

    expect(context.getJobDetail()).andReturn(jobDetail).anyTimes();
    expect(context.getMergedJobDataMap()).andReturn(jobDataMap).anyTimes();
    expect(jobDetail.getKey()).andReturn(new JobKey("TestJob"));
    expect(jobDataMap.getWrappedMap()).andReturn(new HashMap<String,Object>());
    expect(scheduleManagerMock.continueOnMisfire(context)).andReturn(true);

    executionJob.doWork((Map<String, Object>) anyObject());
    expectLastCall().andThrow(new AmbariException("Test Exception")).anyTimes();

    executionJob.finalizeExecution((Map<String, Object>) anyObject());
    expectLastCall().once();

    replay(scheduleManagerMock, executionJob, context, jobDataMap, jobDetail);

    try {
      executionJob.execute(context);
    } catch (Exception ae) {
      assertThat(ae, instanceOf(JobExecutionException.class));
      JobExecutionException je = (JobExecutionException) ae;
      Assert.assertEquals("Test Exception", je.getUnderlyingException().getMessage());
    }

    verify(scheduleManagerMock, executionJob, context, jobDataMap, jobDetail);
  }
}
