public final class FailoverServiceTest {

  @Mock private JobNodeStorage jobNodeStorage;

  @Mock private LocalHostService localHostService;

  @Mock private ServerService serverService;

  @Mock private ShardingService shardingService;

  @Mock private JobScheduleController jobScheduleController;

  private final JobConfiguration jobConfig =
      JobConfigurationFactory.createSimpleJobConfigurationBuilder(
              "testJob", TestJob.class, 3, "0/1 * * * * ?")
          .overwrite(true)
          .build();

  private final FailoverService failoverService = new FailoverService(null, jobConfig);

  @Before
  public void setUp() throws NoSuchFieldException {
    MockitoAnnotations.initMocks(this);
    ReflectionUtils.setFieldValue(failoverService, "jobNodeStorage", jobNodeStorage);
    ReflectionUtils.setFieldValue(failoverService, "localHostService", localHostService);
    ReflectionUtils.setFieldValue(failoverService, "serverService", serverService);
    ReflectionUtils.setFieldValue(failoverService, "shardingService", shardingService);
    when(localHostService.getIp()).thenReturn("mockedIP");
    when(localHostService.getHostName()).thenReturn("mockedHostName");
    when(jobNodeStorage.getJobConfiguration()).thenReturn(jobConfig);
  }

  @Test
  public void assertSetCrashedFailoverFlagWhenItemIsNotAssigned() {
    when(jobNodeStorage.isJobNodeExisted("execution/0/failover")).thenReturn(true);
    failoverService.setCrashedFailoverFlag(0);
    verify(jobNodeStorage).isJobNodeExisted("execution/0/failover");
    verify(jobNodeStorage, times(0)).createJobNodeIfNeeded("leader/failover/items/0");
  }

  @Test
  public void assertSetCrashedFailoverFlagWhenItemIsAssigned() {
    when(jobNodeStorage.isJobNodeExisted("execution/0/failover")).thenReturn(false);
    failoverService.setCrashedFailoverFlag(0);
    verify(jobNodeStorage).isJobNodeExisted("execution/0/failover");
    verify(jobNodeStorage).createJobNodeIfNeeded("leader/failover/items/0");
  }

  @Test
  public void assertFailoverIfUnnecessaryWhenItemsRootNodeNotExisted() {
    when(jobNodeStorage.isJobNodeExisted("leader/failover/items")).thenReturn(false);
    failoverService.failoverIfNecessary();
    verify(jobNodeStorage).isJobNodeExisted("leader/failover/items");
    verify(jobNodeStorage, times(0))
        .executeInLeader(
            eq("leader/failover/latch"), Matchers.<FailoverLeaderExecutionCallback>any());
  }

  @Test
  public void assertFailoverIfUnnecessaryWhenItemsRootNodeIsEmpty() {
    when(jobNodeStorage.isJobNodeExisted("leader/failover/items")).thenReturn(true);
    when(jobNodeStorage.getJobNodeChildrenKeys("leader/failover/items"))
        .thenReturn(Collections.<String>emptyList());
    failoverService.failoverIfNecessary();
    verify(jobNodeStorage).isJobNodeExisted("leader/failover/items");
    verify(jobNodeStorage).getJobNodeChildrenKeys("leader/failover/items");
    verify(jobNodeStorage, times(0))
        .executeInLeader(
            eq("leader/failover/latch"), Matchers.<FailoverLeaderExecutionCallback>any());
  }

  @Test
  public void assertFailoverIfUnnecessaryWhenServerIsNotReady() {
    when(jobNodeStorage.isJobNodeExisted("leader/failover/items")).thenReturn(true);
    when(jobNodeStorage.getJobNodeChildrenKeys("leader/failover/items"))
        .thenReturn(Arrays.asList("0", "1", "2"));
    when(serverService.isLocalhostServerReady()).thenReturn(false);
    failoverService.failoverIfNecessary();
    verify(jobNodeStorage).isJobNodeExisted("leader/failover/items");
    verify(jobNodeStorage).getJobNodeChildrenKeys("leader/failover/items");
    verify(serverService).isLocalhostServerReady();
    verify(jobNodeStorage, times(0))
        .executeInLeader(
            eq("leader/failover/latch"), Matchers.<FailoverLeaderExecutionCallback>any());
  }

  @Test
  public void assertFailoverIfNecessary() {
    when(jobNodeStorage.isJobNodeExisted("leader/failover/items")).thenReturn(true);
    when(jobNodeStorage.getJobNodeChildrenKeys("leader/failover/items"))
        .thenReturn(Arrays.asList("0", "1", "2"));
    when(serverService.isLocalhostServerReady()).thenReturn(true);
    failoverService.failoverIfNecessary();
    verify(jobNodeStorage).isJobNodeExisted("leader/failover/items");
    verify(jobNodeStorage).getJobNodeChildrenKeys("leader/failover/items");
    verify(serverService).isLocalhostServerReady();
    verify(jobNodeStorage)
        .executeInLeader(
            eq("leader/failover/latch"), Matchers.<FailoverLeaderExecutionCallback>any());
  }

  @Test
  public void assertFailoverLeaderExecutionCallbackIfNotNecessary() {
    when(jobNodeStorage.isJobNodeExisted("leader/failover/items")).thenReturn(false);
    failoverService.new FailoverLeaderExecutionCallback().execute();
    verify(jobNodeStorage).isJobNodeExisted("leader/failover/items");
    verify(jobNodeStorage, times(0)).getJobNodeChildrenKeys("leader/failover/items");
  }

  @Test
  public void assertFailoverLeaderExecutionCallbackIfNecessary() {
    when(jobNodeStorage.isJobNodeExisted("leader/failover/items")).thenReturn(true);
    when(jobNodeStorage.getJobNodeChildrenKeys("leader/failover/items"))
        .thenReturn(Arrays.asList("0", "1", "2"));
    when(serverService.isLocalhostServerReady()).thenReturn(true);
    JobRegistry.getInstance().addJobScheduleController("testJob", jobScheduleController);
    failoverService.new FailoverLeaderExecutionCallback().execute();
    verify(jobNodeStorage).isJobNodeExisted("leader/failover/items");
    verify(jobNodeStorage, times(2)).getJobNodeChildrenKeys("leader/failover/items");
    verify(serverService).isLocalhostServerReady();
    verify(jobNodeStorage).fillEphemeralJobNode("execution/0/failover", "mockedIP");
    verify(jobNodeStorage).removeJobNodeIfExisted("leader/failover/items/0");
    verify(jobScheduleController).triggerJob();
  }

  @Test
  public void assertUpdateFailoverComplete() {
    failoverService.updateFailoverComplete(Arrays.asList(0, 1));
    verify(jobNodeStorage).removeJobNodeIfExisted("execution/0/failover");
    verify(jobNodeStorage).removeJobNodeIfExisted("execution/1/failover");
  }

  @Test
  public void assertGetLocalHostFailoverItems() {
    when(jobNodeStorage.getJobNodeChildrenKeys("execution"))
        .thenReturn(Arrays.asList("0", "1", "2"));
    when(jobNodeStorage.isJobNodeExisted("execution/0/failover")).thenReturn(true);
    when(jobNodeStorage.isJobNodeExisted("execution/1/failover")).thenReturn(true);
    when(jobNodeStorage.isJobNodeExisted("execution/2/failover")).thenReturn(false);
    when(jobNodeStorage.getJobNodeDataDirectly("execution/0/failover")).thenReturn("mockedIP");
    when(jobNodeStorage.getJobNodeDataDirectly("execution/1/failover")).thenReturn("otherIP");
    assertThat(failoverService.getLocalHostFailoverItems(), is(Collections.singletonList(0)));
    verify(jobNodeStorage).getJobNodeChildrenKeys("execution");
    verify(localHostService).getIp();
    verify(jobNodeStorage).isJobNodeExisted("execution/0/failover");
    verify(jobNodeStorage).isJobNodeExisted("execution/1/failover");
    verify(jobNodeStorage).getJobNodeDataDirectly("execution/0/failover");
    verify(jobNodeStorage).getJobNodeDataDirectly("execution/1/failover");
  }

  @Test
  public void assertGetLocalHostTakeOffItems() {
    when(shardingService.getLocalHostShardingItems()).thenReturn(Arrays.asList(0, 1, 2));
    when(jobNodeStorage.isJobNodeExisted("execution/0/failover")).thenReturn(true);
    when(jobNodeStorage.isJobNodeExisted("execution/1/failover")).thenReturn(true);
    when(jobNodeStorage.isJobNodeExisted("execution/2/failover")).thenReturn(false);
    assertThat(failoverService.getLocalHostTakeOffItems(), is(Arrays.asList(0, 1)));
    verify(shardingService).getLocalHostShardingItems();
    verify(jobNodeStorage).isJobNodeExisted("execution/0/failover");
    verify(jobNodeStorage).isJobNodeExisted("execution/1/failover");
    verify(jobNodeStorage).isJobNodeExisted("execution/2/failover");
  }

  @Test
  public void assertRemoveFailoverInfo() {
    when(jobNodeStorage.getJobNodeChildrenKeys("execution"))
        .thenReturn(Arrays.asList("0", "1", "2"));
    failoverService.removeFailoverInfo();
    verify(jobNodeStorage).getJobNodeChildrenKeys("execution");
    verify(jobNodeStorage).removeJobNodeIfExisted("execution/0/failover");
    verify(jobNodeStorage).removeJobNodeIfExisted("execution/1/failover");
    verify(jobNodeStorage).removeJobNodeIfExisted("execution/2/failover");
  }
}
public final class JobNodeStorageTest {

  @Mock private CoordinatorRegistryCenter coordinatorRegistryCenter;

  private SimpleJobConfiguration jobConfig =
      JobConfigurationFactory.createSimpleJobConfigurationBuilder(
              "testJob", TestJob.class, 3, "0/1 * * * * ?")
          .overwrite(false)
          .build();

  private JobNodeStorage jobNodeStorage = new JobNodeStorage(coordinatorRegistryCenter, jobConfig);

  @Before
  public void initMocks() throws NoSuchFieldException {
    MockitoAnnotations.initMocks(this);
    ReflectionUtils.setFieldValue(
        jobNodeStorage, "coordinatorRegistryCenter", coordinatorRegistryCenter);
  }

  @Before
  public void reset() {
    JobConfigurationFieldUtil.setSuperFieldValue(jobConfig, "overwrite", false);
  }

  @Test
  public void assertIsJobNodeExisted() {
    when(coordinatorRegistryCenter.isExisted("/testJob/config")).thenReturn(true);
    assertTrue(jobNodeStorage.isJobNodeExisted("config"));
    verify(coordinatorRegistryCenter).isExisted("/testJob/config");
  }

  @Test
  public void assertGetJobNodeData() {
    when(coordinatorRegistryCenter.get("/testJob/config/cron")).thenReturn("0/1 * * * * ?");
    assertThat(jobNodeStorage.getJobNodeData("config/cron"), is("0/1 * * * * ?"));
    verify(coordinatorRegistryCenter).get("/testJob/config/cron");
  }

  @Test
  public void assertGetJobNodeDataDirectly() {
    when(coordinatorRegistryCenter.getDirectly("/testJob/config/cron")).thenReturn("0/1 * * * * ?");
    assertThat(jobNodeStorage.getJobNodeDataDirectly("config/cron"), is("0/1 * * * * ?"));
    verify(coordinatorRegistryCenter).getDirectly("/testJob/config/cron");
  }

  @Test
  public void assertGetJobNodeChildrenKeys() {
    when(coordinatorRegistryCenter.getChildrenKeys("/testJob/servers"))
        .thenReturn(Arrays.asList("host0", "host1"));
    assertThat(
        jobNodeStorage.getJobNodeChildrenKeys("servers"), is(Arrays.asList("host0", "host1")));
    verify(coordinatorRegistryCenter).getChildrenKeys("/testJob/servers");
  }

  @Test
  public void assertCreateJobNodeIfNeeded() {
    when(coordinatorRegistryCenter.isExisted("/testJob")).thenReturn(true);
    when(coordinatorRegistryCenter.isExisted("/testJob/config")).thenReturn(false);
    jobNodeStorage.createJobNodeIfNeeded("config");
    verify(coordinatorRegistryCenter).isExisted("/testJob");
    verify(coordinatorRegistryCenter).isExisted("/testJob/config");
    verify(coordinatorRegistryCenter).persist("/testJob/config", "");
  }

  @Test
  public void assertCreateJobNodeIfRootJobNodeIsNotExist() {
    when(coordinatorRegistryCenter.isExisted("/testJob")).thenReturn(false);
    when(coordinatorRegistryCenter.isExisted("/testJob/config")).thenReturn(true);
    jobNodeStorage.createJobNodeIfNeeded("config");
    verify(coordinatorRegistryCenter).isExisted("/testJob");
    verify(coordinatorRegistryCenter, times(0)).isExisted("/testJob/config");
    verify(coordinatorRegistryCenter, times(0)).persist("/testJob/config", "");
  }

  @Test
  public void assertCreateJobNodeIfNotNeeded() {
    when(coordinatorRegistryCenter.isExisted("/testJob")).thenReturn(true);
    when(coordinatorRegistryCenter.isExisted("/testJob/config")).thenReturn(true);
    jobNodeStorage.createJobNodeIfNeeded("config");
    verify(coordinatorRegistryCenter).isExisted("/testJob");
    verify(coordinatorRegistryCenter).isExisted("/testJob/config");
    verify(coordinatorRegistryCenter, times(0)).persist("/testJob/config", "");
  }

  @Test
  public void assertRemoveJobNodeIfNeeded() {
    when(coordinatorRegistryCenter.isExisted("/testJob/config")).thenReturn(true);
    jobNodeStorage.removeJobNodeIfExisted("config");
    verify(coordinatorRegistryCenter).isExisted("/testJob/config");
    verify(coordinatorRegistryCenter).remove("/testJob/config");
  }

  @Test
  public void assertRemoveJobNodeIfNotNeeded() {
    when(coordinatorRegistryCenter.isExisted("/testJob/config")).thenReturn(false);
    jobNodeStorage.removeJobNodeIfExisted("config");
    verify(coordinatorRegistryCenter).isExisted("/testJob/config");
    verify(coordinatorRegistryCenter, times(0)).remove("/testJob/config");
  }

  @Test
  public void assertFillJobNodeIfNotNullAndOverwriteDisabled() {
    when(coordinatorRegistryCenter.isExisted("/testJob/config/cron")).thenReturn(true);
    jobNodeStorage.fillJobNodeIfNullOrOverwrite("config/cron", "0/1 * * * * ?");
    verify(coordinatorRegistryCenter).isExisted("/testJob/config/cron");
    verify(coordinatorRegistryCenter, times(0)).persist("/testJob/config/cron", "0/1 * * * * ?");
  }

  @Test
  public void assertFillJobNodeIfNotNullAndOverwriteEnabledButValueSame()
      throws NoSuchFieldException {
    when(coordinatorRegistryCenter.isExisted("/testJob/config/cron")).thenReturn(true);
    when(coordinatorRegistryCenter.getDirectly("/testJob/config/cron")).thenReturn("0/1 * * * * ?");
    JobConfigurationFieldUtil.setSuperFieldValue(jobConfig, "overwrite", true);
    jobNodeStorage.fillJobNodeIfNullOrOverwrite("config/cron", "0/1 * * * * ?");
    verify(coordinatorRegistryCenter).isExisted("/testJob/config/cron");
    verify(coordinatorRegistryCenter).getDirectly("/testJob/config/cron");
    verify(coordinatorRegistryCenter, times(0)).persist("/testJob/config", "0/1 * * * * ?");
  }

  @Test
  public void assertFillJobNodeIfNotNullAndOverwriteEnabledAndValueDifferent()
      throws NoSuchFieldException {
    when(coordinatorRegistryCenter.isExisted("/testJob/config/cron")).thenReturn(true);
    when(coordinatorRegistryCenter.getDirectly("/testJob/config/cron")).thenReturn("0/1 * * * * ?");
    JobConfigurationFieldUtil.setSuperFieldValue(jobConfig, "overwrite", true);
    jobNodeStorage.fillJobNodeIfNullOrOverwrite("config/cron", "0/2 * * * * ?");
    verify(coordinatorRegistryCenter).isExisted("/testJob/config/cron");
    verify(coordinatorRegistryCenter).getDirectly("/testJob/config/cron");
    verify(coordinatorRegistryCenter).persist("/testJob/config/cron", "0/2 * * * * ?");
  }

  @Test
  public void assertFillJobNodeIfNull() {
    when(coordinatorRegistryCenter.isExisted("/testJob/config/cron")).thenReturn(false);
    jobNodeStorage.fillJobNodeIfNullOrOverwrite("config/cron", "0/2 * * * * ?");
    verify(coordinatorRegistryCenter).isExisted("/testJob/config/cron");
    verify(coordinatorRegistryCenter).persist("/testJob/config/cron", "0/2 * * * * ?");
  }

  @Test
  public void assertFillEphemeralJobNode() {
    jobNodeStorage.fillEphemeralJobNode("config/cron", "0/1 * * * * ?");
    verify(coordinatorRegistryCenter).persistEphemeral("/testJob/config/cron", "0/1 * * * * ?");
  }

  @Test
  public void assertUpdateJobNode() {
    jobNodeStorage.updateJobNode("config/cron", "0/1 * * * * ?");
    verify(coordinatorRegistryCenter).update("/testJob/config/cron", "0/1 * * * * ?");
  }

  @Test
  public void assertReplaceJobNode() {
    jobNodeStorage.replaceJobNode("config/cron", "0/1 * * * * ?");
    verify(coordinatorRegistryCenter).persist("/testJob/config/cron", "0/1 * * * * ?");
  }

  @Test
  public void assertExecuteInTransactionSuccess() throws Exception {
    CuratorFramework client = mock(CuratorFramework.class);
    CuratorTransaction curatorTransaction = mock(CuratorTransaction.class);
    TransactionCheckBuilder transactionCheckBuilder = mock(TransactionCheckBuilder.class);
    CuratorTransactionBridge curatorTransactionBridge = mock(CuratorTransactionBridge.class);
    CuratorTransactionFinal curatorTransactionFinal = mock(CuratorTransactionFinal.class);
    when(coordinatorRegistryCenter.getRawClient()).thenReturn(client);
    when(client.inTransaction()).thenReturn(curatorTransaction);
    when(curatorTransaction.check()).thenReturn(transactionCheckBuilder);
    when(transactionCheckBuilder.forPath("/")).thenReturn(curatorTransactionBridge);
    when(curatorTransactionBridge.and()).thenReturn(curatorTransactionFinal);
    TransactionCreateBuilder transactionCreateBuilder = mock(TransactionCreateBuilder.class);
    when(curatorTransactionFinal.create()).thenReturn(transactionCreateBuilder);
    when(transactionCreateBuilder.forPath("/test_transaction"))
        .thenReturn(curatorTransactionBridge);
    when(curatorTransactionBridge.and()).thenReturn(curatorTransactionFinal);
    jobNodeStorage.executeInTransaction(
        new TransactionExecutionCallback() {

          @Override
          public void execute(final CuratorTransactionFinal curatorTransactionFinal)
              throws Exception {
            curatorTransactionFinal.create().forPath("/test_transaction").and();
          }
        });
    verify(coordinatorRegistryCenter).getRawClient();
    verify(client).inTransaction();
    verify(curatorTransaction).check();
    verify(transactionCheckBuilder).forPath("/");
    verify(curatorTransactionBridge, times(2)).and();
    verify(curatorTransactionFinal).create();
    verify(transactionCreateBuilder).forPath("/test_transaction");
    verify(curatorTransactionFinal).commit();
  }

  @Test(expected = RuntimeException.class)
  public void assertExecuteInTransactionFailure() throws Exception {
    CuratorFramework client = mock(CuratorFramework.class);
    CuratorTransaction curatorTransaction = mock(CuratorTransaction.class);
    TransactionCheckBuilder transactionCheckBuilder = mock(TransactionCheckBuilder.class);
    CuratorTransactionBridge curatorTransactionBridge = mock(CuratorTransactionBridge.class);
    CuratorTransactionFinal curatorTransactionFinal = mock(CuratorTransactionFinal.class);
    when(coordinatorRegistryCenter.getRawClient()).thenReturn(client);
    when(client.inTransaction()).thenReturn(curatorTransaction);
    when(curatorTransaction.check()).thenReturn(transactionCheckBuilder);
    when(transactionCheckBuilder.forPath("/")).thenReturn(curatorTransactionBridge);
    when(curatorTransactionBridge.and()).thenReturn(curatorTransactionFinal);
    TransactionCreateBuilder transactionCreateBuilder = mock(TransactionCreateBuilder.class);
    when(curatorTransactionFinal.create()).thenReturn(transactionCreateBuilder);
    when(transactionCreateBuilder.forPath("/test_transaction"))
        .thenReturn(curatorTransactionBridge);
    when(curatorTransactionBridge.and()).thenThrow(new RuntimeException());
    jobNodeStorage.executeInTransaction(
        new TransactionExecutionCallback() {

          @Override
          public void execute(final CuratorTransactionFinal curatorTransactionFinal)
              throws Exception {
            curatorTransactionFinal.create().forPath("/test_transaction").and();
          }
        });
    verify(coordinatorRegistryCenter).getRawClient();
    verify(client).inTransaction();
    verify(curatorTransaction).check();
    verify(transactionCheckBuilder).forPath("/");
    verify(curatorTransactionBridge, times(2)).and();
    verify(curatorTransactionFinal).create();
    verify(transactionCreateBuilder).forPath("/test_transaction");
    verify(curatorTransactionFinal, times(0)).commit();
  }

  @Test
  public void assertAddConnectionStateListener() {
    CuratorFramework client = mock(CuratorFramework.class);
    @SuppressWarnings("unchecked")
    Listenable<ConnectionStateListener> listeners = mock(Listenable.class);
    ConnectionStateListener listener = mock(ConnectionStateListener.class);
    when(client.getConnectionStateListenable()).thenReturn(listeners);
    when(coordinatorRegistryCenter.getRawClient()).thenReturn(client);
    jobNodeStorage.addConnectionStateListener(listener);
    verify(listeners).addListener(listener);
  }

  @Test
  public void assertAddDataListener() {
    TreeCache treeCache = mock(TreeCache.class);
    @SuppressWarnings("unchecked")
    Listenable<TreeCacheListener> listeners = mock(Listenable.class);
    TreeCacheListener listener = mock(TreeCacheListener.class);
    when(treeCache.getListenable()).thenReturn(listeners);
    when(coordinatorRegistryCenter.getRawCache("/testJob")).thenReturn(treeCache);
    jobNodeStorage.addDataListener(listener);
    verify(listeners).addListener(listener);
  }

  @Test
  public void assertGetRegistryCenterTime() {
    when(coordinatorRegistryCenter.getRegistryCenterTime("/testJob/systemTime/current"))
        .thenReturn(0L);
    assertThat(jobNodeStorage.getRegistryCenterTime(), is(0L));
    verify(coordinatorRegistryCenter).getRegistryCenterTime("/testJob/systemTime/current");
  }

  @Test
  public void assertGetJobConfiguration() {
    assertThat(jobNodeStorage.getJobConfiguration(), is((JobConfiguration) jobConfig));
  }
}