content: // 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.cloudstack.resource;
  
  import java.lang.reflect.Field;
  import java.time.Duration;
  import java.time.Instant;
  import java.time.ZoneId;
  import java.time.ZonedDateTime;
  import java.time.zone.ZoneRules;
  import java.util.ArrayList;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.HashSet;
  import java.util.List;
  import java.util.concurrent.TimeUnit;
  
  import org.apache.cloudstack.api.command.admin.resource.PurgeExpungedResourcesCmd;
  import org.apache.cloudstack.backup.BackupVO;
  import org.apache.cloudstack.backup.dao.BackupDao;
  import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
  import org.apache.cloudstack.framework.config.ConfigKey;
  import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao;
  import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
  import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
  import org.apache.commons.collections.CollectionUtils;
  import org.junit.Assert;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.mockito.InjectMocks;
  import org.mockito.Mock;
  import org.mockito.Mockito;
  import org.mockito.Spy;
  import org.mockito.junit.MockitoJUnitRunner;
  
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.ha.HighAvailabilityManager;
  import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.InlineLoadBalancerNicMapDao;
  import com.cloud.network.dao.LoadBalancerVMMapDao;
  import com.cloud.network.dao.OpRouterMonitorServiceDao;
  import com.cloud.network.rules.dao.PortForwardingRulesDao;
  import com.cloud.offering.ServiceOffering;
  import com.cloud.secstorage.CommandExecLogDao;
  import com.cloud.service.dao.ServiceOfferingDetailsDao;
  import com.cloud.storage.SnapshotVO;
  import com.cloud.storage.VolumeVO;
  import com.cloud.storage.dao.SnapshotDao;
  import com.cloud.storage.dao.SnapshotDetailsDao;
  import com.cloud.storage.dao.VolumeDao;
  import com.cloud.storage.dao.VolumeDetailsDao;
  import com.cloud.utils.Pair;
  import com.cloud.utils.db.SearchBuilder;
  import com.cloud.utils.db.SearchCriteria;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.vm.ItWorkDao;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.VirtualMachine;
  import com.cloud.vm.dao.ConsoleSessionDao;
  import com.cloud.vm.dao.NicDao;
  import com.cloud.vm.dao.NicDetailsDao;
  import com.cloud.vm.dao.NicExtraDhcpOptionDao;
  import com.cloud.vm.dao.NicSecondaryIpDao;
  import com.cloud.vm.dao.VMInstanceDetailsDao;
  import com.cloud.vm.dao.VMInstanceDao;
  import com.cloud.vm.snapshot.VMSnapshotVO;
  import com.cloud.vm.snapshot.dao.VMSnapshotDao;
  import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
  
  @RunWith(MockitoJUnitRunner.class)
  public class ResourceCleanupServiceImplTest {
  
      @Mock
      VMInstanceDao vmInstanceDao;
      @Mock
      VolumeDao volumeDao;
      @Mock
      VolumeDetailsDao volumeDetailsDao;
      @Mock
      VolumeDataStoreDao volumeDataStoreDao;
      @Mock
      SnapshotDao snapshotDao;
      @Mock
      SnapshotDetailsDao snapshotDetailsDao;
      @Mock
      SnapshotDataStoreDao snapshotDataStoreDao;
      @Mock
      NicDao nicDao;
      @Mock
      NicDetailsDao nicDetailsDao;
      @Mock
      NicExtraDhcpOptionDao nicExtraDhcpOptionDao;
      @Mock
      InlineLoadBalancerNicMapDao inlineLoadBalancerNicMapDao;
      @Mock
      VMSnapshotDao vmSnapshotDao;
      @Mock
      VMSnapshotDetailsDao vmSnapshotDetailsDao;
      @Mock
      VMInstanceDetailsDao vmInstanceDetailsDao;
      @Mock
      AutoScaleVmGroupVmMapDao autoScaleVmGroupVmMapDao;
      @Mock
      CommandExecLogDao commandExecLogDao;
      @Mock
      NetworkOrchestrationService networkOrchestrationService;
      @Mock
      LoadBalancerVMMapDao loadBalancerVMMapDao;
      @Mock
      NicSecondaryIpDao nicSecondaryIpDao;
      @Mock
      HighAvailabilityManager highAvailabilityManager;
      @Mock
      ItWorkDao itWorkDao;
      @Mock
      OpRouterMonitorServiceDao opRouterMonitorServiceDao;
      @Mock
      PortForwardingRulesDao portForwardingRulesDao;
      @Mock
      IPAddressDao ipAddressDao;
      @Mock
      VmWorkJobDao vmWorkJobDao;
      @Mock
      ConsoleSessionDao consoleSessionDao;
      @Mock
      ServiceOfferingDetailsDao serviceOfferingDetailsDao;
      @Mock
      BackupDao backupDao;
  
      @Spy
      @InjectMocks
      ResourceCleanupServiceImpl resourceCleanupService = Mockito.spy(new ResourceCleanupServiceImpl());
  
      List<Long> ids = List.of(1L, 2L, 3L);
      Long batchSize = 100L;
  
      private void overrideConfigValue(final ConfigKey configKey, final Object value) {
          try {
              Field f = ConfigKey.class.getDeclaredField("_value");
              f.setAccessible(true);
              f.set(configKey, value);
          } catch (IllegalAccessException | NoSuchFieldException e) {
              Assert.fail(e.getMessage());
          }
      }
  
  
      @Test
      public void testPurgeLinkedSnapshotEntitiesNoSnapshots() {
          resourceCleanupService.purgeLinkedSnapshotEntities(new ArrayList<>(), batchSize);
          Mockito.verify(snapshotDetailsDao, Mockito.never())
                  .batchExpungeForResources(Mockito.anyList(), Mockito.anyLong());
          Mockito.verify(snapshotDataStoreDao, Mockito.never())
                  .expungeBySnapshotList(Mockito.anyList(), Mockito.anyLong());
      }
  
  
      @Test
      public void testPurgeLinkedSnapshotEntities() {
          Mockito.when(snapshotDetailsDao.batchExpungeForResources(ids, batchSize)).thenReturn(2L);
          Mockito.when(snapshotDataStoreDao.expungeBySnapshotList(ids, batchSize)).thenReturn(2);
          resourceCleanupService.purgeLinkedSnapshotEntities(ids, batchSize);
          Mockito.verify(snapshotDetailsDao, Mockito.times(1))
                  .batchExpungeForResources(ids, batchSize);
          Mockito.verify(snapshotDataStoreDao, Mockito.times(1))
                  .expungeBySnapshotList(ids, batchSize);
      }
  
      @Test
      public void testPurgeVolumeSnapshotsNoVolumes() {
          Assert.assertEquals(0, resourceCleanupService.purgeVolumeSnapshots(new ArrayList<>(), 50L));
          Mockito.verify(snapshotDao, Mockito.never()).createSearchBuilder();
      }
  
      @Test
      public void testPurgeVolumeSnapshots() {
          SearchBuilder<SnapshotVO> sb = Mockito.mock(SearchBuilder.class);
          Mockito.when(sb.entity()).thenReturn(Mockito.mock(SnapshotVO.class));
          Mockito.when(sb.create()).thenReturn(Mockito.mock(SearchCriteria.class));
          Mockito.when(snapshotDao.createSearchBuilder()).thenReturn(sb);
          Assert.assertEquals(0, resourceCleanupService.purgeVolumeSnapshots(new ArrayList<>(), 50L));
          Mockito.when(snapshotDao.searchIncludingRemoved(Mockito.any(), Mockito.any(),
                  Mockito.any(), Mockito.anyBoolean()))
                  .thenReturn(List.of(Mockito.mock(SnapshotVO.class), Mockito.mock(SnapshotVO.class)));
          Mockito.when(snapshotDao.expungeList(Mockito.anyList())).thenReturn(2);
          Assert.assertEquals(2, resourceCleanupService.purgeVolumeSnapshots(ids, batchSize));
      }
  
      @Test
      public void testPurgeLinkedVolumeEntitiesNoVolumes() {
          resourceCleanupService.purgeLinkedVolumeEntities(new ArrayList<>(), 50L);
          Mockito.verify(volumeDetailsDao, Mockito.never()).batchExpungeForResources(Mockito.anyList(),
                  Mockito.anyLong());
      }
  
      @Test
      public void testPurgeLinkedVolumeEntities() {
          Mockito.when(volumeDetailsDao.batchExpungeForResources(ids, batchSize)).thenReturn(2L);
          Mockito.when(volumeDataStoreDao.expungeByVolumeList(ids, batchSize)).thenReturn(2);
          Mockito.doReturn(2L).when(resourceCleanupService).purgeVolumeSnapshots(ids, batchSize);
          resourceCleanupService.purgeLinkedVolumeEntities(ids, batchSize);
          Mockito.verify(volumeDetailsDao, Mockito.times(1))
                  .batchExpungeForResources(ids, batchSize);
          Mockito.verify(volumeDataStoreDao, Mockito.times(1))
                  .expungeByVolumeList(ids, batchSize);
          Mockito.verify(resourceCleanupService, Mockito.times(1))
                  .purgeVolumeSnapshots(ids, batchSize);
      }
  
      @Test
      public void testPurgeVMVolumesNoVms() {
          Assert.assertEquals(0, resourceCleanupService.purgeVMVolumes(new ArrayList<>(), 50L));
          Mockito.verify(volumeDao, Mockito.never()).searchRemovedByVms(Mockito.anyList(), Mockito.anyLong());
      }
  
      @Test
      public void testPurgeVMVolumes() {
          Mockito.when(volumeDao.searchRemovedByVms(ids, batchSize))
                  .thenReturn(List.of(Mockito.mock(VolumeVO.class), Mockito.mock(VolumeVO.class)));
          Mockito.when(volumeDao.expungeList(Mockito.anyList())).thenReturn(2);
          Mockito.doNothing().when(resourceCleanupService).purgeLinkedVolumeEntities(Mockito.anyList(),
                  Mockito.eq(batchSize));
          Assert.assertEquals(2, resourceCleanupService.purgeVMVolumes(ids, batchSize));
      }
  
      @Test
      public void testPurgeLinkedNicEntitiesNoNics() {
          resourceCleanupService.purgeLinkedNicEntities(new ArrayList<>(), batchSize);
          Mockito.verify(nicDetailsDao, Mockito.never())
                  .batchExpungeForResources(ids, batchSize);
          Mockito.verify(nicExtraDhcpOptionDao, Mockito.never())
                  .expungeByNicList(ids, batchSize);
          Mockito.verify(inlineLoadBalancerNicMapDao, Mockito.never())
                  .expungeByNicList(ids, batchSize);
      }
  
      @Test
      public void testPurgeLinkedNicEntities() {
          Mockito.when(nicDetailsDao.batchExpungeForResources(ids, batchSize)).thenReturn(2L);
          Mockito.when(nicExtraDhcpOptionDao.expungeByNicList(ids, batchSize)).thenReturn(2);
          Mockito.when(inlineLoadBalancerNicMapDao.expungeByNicList(ids, batchSize)).thenReturn(2);
          resourceCleanupService.purgeLinkedNicEntities(ids, batchSize);
          Mockito.verify(nicDetailsDao, Mockito.times(1))
                  .batchExpungeForResources(ids, batchSize);
          Mockito.verify(nicExtraDhcpOptionDao, Mockito.times(1))
                  .expungeByNicList(ids, batchSize);
          Mockito.verify(inlineLoadBalancerNicMapDao, Mockito.times(1))
                  .expungeByNicList(ids, batchSize);
      }
  
      @Test
      public void testPurgeVMNicsNoVms() {
          Assert.assertEquals(0, resourceCleanupService.purgeVMNics(new ArrayList<>(), 50L));
          Mockito.verify(nicDao, Mockito.never()).searchRemovedByVms(Mockito.anyList(), Mockito.anyLong());
      }
  
      @Test
      public void testPurgeVMNics() {
          Mockito.when(nicDao.searchRemovedByVms(ids, batchSize))
                  .thenReturn(List.of(Mockito.mock(NicVO.class), Mockito.mock(NicVO.class)));
          Mockito.when(nicDao.expungeList(Mockito.anyList())).thenReturn(2);
          Mockito.doNothing().when(resourceCleanupService).purgeLinkedNicEntities(Mockito.anyList(),
                  Mockito.eq(batchSize));
          Assert.assertEquals(2, resourceCleanupService.purgeVMNics(ids, batchSize));
      }
  
      @Test
      public void testPurgeVMSnapshotsNoVms() {
          Assert.assertEquals(0, resourceCleanupService.purgeVMSnapshots(new ArrayList<>(), 50L));
          Mockito.verify(vmSnapshotDao, Mockito.never()).searchRemovedByVms(Mockito.anyList(), Mockito.anyLong());
      }
  
      @Test
      public void testPurgeVMSnapshots() {
          Mockito.when(vmSnapshotDao.searchRemovedByVms(ids, batchSize))
                  .thenReturn(List.of(Mockito.mock(VMSnapshotVO.class), Mockito.mock(VMSnapshotVO.class)));
          Mockito.when(vmSnapshotDao.expungeList(Mockito.anyList())).thenReturn(2);
          Mockito.when(vmSnapshotDetailsDao.batchExpungeForResources(Mockito.anyList(),
                  Mockito.eq(batchSize))).thenReturn(2L);
          Assert.assertEquals(2, resourceCleanupService.purgeVMSnapshots(ids, batchSize));
      }
  
      @Test
      public void testPurgeLinkedVMEntitiesNoVms() {
          resourceCleanupService.purgeLinkedVMEntities(new ArrayList<>(), 50L);
          Mockito.verify(resourceCleanupService, Mockito.never()).purgeVMVolumes(Mockito.anyList(),
                  Mockito.anyLong());
          Mockito.verify(vmInstanceDetailsDao, Mockito.never())
                  .batchExpungeForResources(Mockito.anyList(), Mockito.anyLong());
      }
  
      @Test
      public void testPurgeLinkedVMEntities() {
          Mockito.doReturn(2L).when(resourceCleanupService).purgeVMVolumes(Mockito.anyList(),
                  Mockito.eq(batchSize));
          Mockito.doReturn(2L).when(resourceCleanupService).purgeVMNics(Mockito.anyList(),
                  Mockito.eq(batchSize));
          Mockito.when(vmInstanceDetailsDao.batchExpungeForResources(Mockito.anyList(), Mockito.anyLong())).thenReturn(2L);
          Mockito.doReturn(2L).when(resourceCleanupService).purgeVMSnapshots(Mockito.anyList(),
                  Mockito.eq(batchSize));
          Mockito.when(autoScaleVmGroupVmMapDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(commandExecLogDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(loadBalancerVMMapDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(nicSecondaryIpDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(highAvailabilityManager.expungeWorkItemsByVmList(Mockito.anyList(), Mockito.anyLong()))
                  .thenReturn(2);
          Mockito.when(itWorkDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(opRouterMonitorServiceDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(portForwardingRulesDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(ipAddressDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(vmWorkJobDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
          Mockito.when(consoleSessionDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2);
  
          resourceCleanupService.purgeLinkedVMEntities(ids, batchSize);
  
          Mockito.verify(resourceCleanupService, Mockito.times(1)).purgeVMVolumes(ids, batchSize);
          Mockito.verify(resourceCleanupService, Mockito.times(1)).purgeVMNics(ids, batchSize);
          Mockito.verify(vmInstanceDetailsDao, Mockito.times(1))
                  .batchExpungeForResources(ids, batchSize);
          Mockito.verify(resourceCleanupService, Mockito.times(1))
                  .purgeVMSnapshots(ids, batchSize);
          Mockito.verify(autoScaleVmGroupVmMapDao, Mockito.times(1))
                  .expungeByVmList(ids, batchSize);
          Mockito.verify(commandExecLogDao, Mockito.times(1)).expungeByVmList(ids, batchSize);
          Mockito.verify(loadBalancerVMMapDao, Mockito.times(1)).expungeByVmList(ids, batchSize);
          Mockito.verify(nicSecondaryIpDao, Mockito.times(1)).expungeByVmList(ids, batchSize);
          Mockito.verify(highAvailabilityManager, Mockito.times(1)).
                  expungeWorkItemsByVmList(ids, batchSize);
          Mockito.verify(itWorkDao, Mockito.times(1)).expungeByVmList(ids, batchSize);
          Mockito.verify(opRouterMonitorServiceDao, Mockito.times(1))
                  .expungeByVmList(ids, batchSize);
          Mockito.verify(portForwardingRulesDao, Mockito.times(1)).expungeByVmList(ids, batchSize);
          Mockito.verify(ipAddressDao, Mockito.times(1)).expungeByVmList(ids, batchSize);
          Mockito.verify(vmWorkJobDao, Mockito.times(1)).expungeByVmList(ids, batchSize);
          Mockito.verify(consoleSessionDao, Mockito.times(1)).expungeByVmList(ids, batchSize);
      }
  
      @Test
      public void testGetVmIdsWithActiveVolumeSnapshotsNoVms() {
          Assert.assertTrue(CollectionUtils.isEmpty(
                  resourceCleanupService.getVmIdsWithActiveVolumeSnapshots(new ArrayList<>())));
      }
  
      @Test
      public void testGetVmIdsWithActiveVolumeSnapshots() {
          VolumeVO vol1 = Mockito.mock(VolumeVO.class);
          Mockito.when(vol1.getId()).thenReturn(1L);
          Mockito.when(vol1.getInstanceId()).thenReturn(1L);
          VolumeVO vol2 = Mockito.mock(VolumeVO.class);
          Mockito.when(vol2.getId()).thenReturn(2L);
          Mockito.when(volumeDao.searchRemovedByVms(ids, null)).thenReturn(List.of(vol1, vol2));
          SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class);
          Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L);
          Mockito.when(snapshotDao.searchByVolumes(Mockito.anyList())).thenReturn(List.of(snapshotVO));
          HashSet<Long> vmIds = resourceCleanupService.getVmIdsWithActiveVolumeSnapshots(ids);
          Assert.assertTrue(CollectionUtils.isNotEmpty(vmIds));
          Assert.assertEquals(1, vmIds.size());
          Assert.assertEquals(1L, vmIds.toArray()[0]);
      }
  
      @Test
      public void testGetFilteredVmIdsForSnapshots() {
          List<Long> skippedVmIds = ids.subList(0, 2);
          Long notSkippedVmIds = ids.get(2);
          VMSnapshotVO vmSnapshotVO = Mockito.mock(VMSnapshotVO.class);
          Mockito.when(vmSnapshotVO.getVmId()).thenReturn(1L);
          Mockito.when(vmSnapshotDao.searchByVms(Mockito.anyList())).thenReturn(List.of(vmSnapshotVO));
          HashSet<Long> set = new HashSet<>();
          set.add(1L);
          Mockito.doReturn(set).when(resourceCleanupService).getVmIdsWithActiveVolumeSnapshots(ids);
          BackupVO backupVO = Mockito.mock(BackupVO.class);
          Mockito.when(backupVO.getVmId()).thenReturn(2L);
          Mockito.when(backupDao.searchByVmIds(Mockito.anyList())).thenReturn(List.of(backupVO));
          Pair<List<Long>, List<Long>> result = resourceCleanupService.getFilteredVmIdsForSnapshotsAndBackups(new ArrayList<>(ids));
          Assert.assertEquals(1, result.first().size());
          Assert.assertEquals(2, result.second().size());
          Assert.assertEquals(notSkippedVmIds, result.first().get(0));
          Assert.assertEquals(skippedVmIds, result.second());
      }
  
      @Test
      public void testGetVmIdsWithNoActiveSnapshotsAndBackups() {
          VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
          Mockito.when(vm1.getId()).thenReturn(ids.get(0));
          VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
          Mockito.when(vm2.getId()).thenReturn(ids.get(1));
          VMInstanceVO vm3 = Mockito.mock(VMInstanceVO.class);
          Mockito.when(vm3.getId()).thenReturn(ids.get(2));
          Mockito.when(vmInstanceDao.searchRemovedByRemoveDate(Mockito.any(), Mockito.any(),
                          Mockito.anyLong(), Mockito.anyList())).thenReturn(List.of(vm1, vm2, vm3));
          List<Long> skippedVmIds = ids.subList(0, 2);
          Long notSkippedVmIds = ids.get(2);
          VMSnapshotVO vmSnapshotVO = Mockito.mock(VMSnapshotVO.class);
          Mockito.when(vmSnapshotVO.getVmId()).thenReturn(1L);
          Mockito.when(vmSnapshotDao.searchByVms(Mockito.anyList())).thenReturn(List.of(vmSnapshotVO));
          HashSet<Long> set = new HashSet<>();
          set.add(1L);
          Mockito.doReturn(set).when(resourceCleanupService).getVmIdsWithActiveVolumeSnapshots(Mockito.anyList());
          BackupVO backupVO = Mockito.mock(BackupVO.class);
          Mockito.when(backupVO.getVmId()).thenReturn(2L);
          Mockito.when(backupDao.searchByVmIds(Mockito.anyList())).thenReturn(List.of(backupVO));
  
          Pair<List<Long>, List<Long>> result =
                  resourceCleanupService.getVmIdsWithNoActiveSnapshotsAndBackups(new Date(), new Date(), batchSize,
                          new ArrayList<>());
          Assert.assertEquals(1, result.first().size());
          Assert.assertEquals(2, result.second().size());
          Assert.assertEquals(notSkippedVmIds, result.first().get(0));
          Assert.assertEquals(skippedVmIds, result.second());
      }
  
      @Test
      public void testPurgeVMEntitiesNoVms() {
          Mockito.when(vmInstanceDao.searchRemovedByRemoveDate(Mockito.any(), Mockito.any(),
                  Mockito.anyLong(), Mockito.anyList())).thenReturn(new ArrayList<>());
          Assert.assertEquals(0, resourceCleanupService.purgeVMEntities(batchSize, new Date(), new Date()));
      }
  
      @Test
      public void testPurgeVMEntities() {
          Mockito.doReturn(new Pair<>(ids, new ArrayList<>())).when(resourceCleanupService)
                  .getVmIdsWithNoActiveSnapshotsAndBackups(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyList());
          Mockito.when(vmInstanceDao.expungeList(ids)).thenReturn(ids.size());
          Assert.assertEquals(ids.size(), resourceCleanupService.purgeVMEntities(batchSize, new Date(), new Date()));
      }
  
      @Test
      public void testExpungeVMEntityFiltered() {
          Mockito.doReturn(new Pair<>(new ArrayList<>(), List.of(ids.get(0)))).when(resourceCleanupService)
                  .getFilteredVmIdsForSnapshotsAndBackups(Mockito.anyList());
          Assert.assertFalse(resourceCleanupService.purgeVMEntity(ids.get(0)));
      }
  
      @Test
      public void testPurgeVMEntityFiltered() {
          Mockito.doReturn(new Pair<>(List.of(ids.get(0)), new ArrayList<>())).when(resourceCleanupService)
                  .getFilteredVmIdsForSnapshotsAndBackups(Mockito.anyList());
          Mockito.doNothing().when(resourceCleanupService)
                  .purgeLinkedVMEntities(Mockito.anyList(), Mockito.anyLong());
          Mockito.when(vmInstanceDao.expunge(ids.get(0))).thenReturn(true);
          Assert.assertTrue(resourceCleanupService.purgeVMEntity(ids.get(0)));
      }
  
      @Test
      public void testPurgeVMEntity() {
          Mockito.doReturn(new Pair<>(List.of(ids.get(0)), new ArrayList<>())).when(resourceCleanupService)
                  .getFilteredVmIdsForSnapshotsAndBackups(Mockito.anyList());
          Mockito.doNothing().when(resourceCleanupService)
                  .purgeLinkedVMEntities(Mockito.anyList(), Mockito.anyLong());
          Mockito.when(vmInstanceDao.expunge(ids.get(0))).thenReturn(true);
          Assert.assertTrue(resourceCleanupService.purgeVMEntity(ids.get(0)));
      }
  
      @Test
      public void testPurgeEntities() {
          Mockito.doReturn((long)ids.size()).when(resourceCleanupService)
                  .purgeVMEntities(Mockito.anyLong(), Mockito.any(), Mockito.any());
          long result = resourceCleanupService.purgeEntities(
                  List.of(ResourceCleanupService.ResourceType.VirtualMachine), batchSize, new Date(), new Date());
          Assert.assertEquals(ids.size(), result);
      }
  
      @Test(expected = InvalidParameterValueException.class)
      public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParamsInvalidResourceType() {
          resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams("Volume",
                  new Date(), new Date(), batchSize);
      }
  
      @Test(expected = InvalidParameterValueException.class)
      public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParamsInvalidBatchSize() {
          resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams(
                  ResourceCleanupService.ResourceType.VirtualMachine.toString(),
                  new Date(), new Date(), -1L);
      }
  
      @Test(expected = InvalidParameterValueException.class)
      public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParamsInvalidDates() {
          Calendar cal = Calendar.getInstance();
          Date startDate = new Date();
          cal.setTime(startDate);
          cal.add(Calendar.DATE, -1);
          Date endDate = cal.getTime();
          resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams(
                  ResourceCleanupService.ResourceType.VirtualMachine.toString(),
                  startDate, endDate, 100L);
      }
  
      @Test
      public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParams() {
          Calendar cal = Calendar.getInstance();
          Date endDate = new Date();
          cal.setTime(endDate);
          cal.add(Calendar.DATE, -1);
          Date startDate = cal.getTime();
          ResourceCleanupService.ResourceType type =
                  resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams(
                  ResourceCleanupService.ResourceType.VirtualMachine.toString(),
                  startDate, endDate, 100L);
          Assert.assertEquals(ResourceCleanupService.ResourceType.VirtualMachine, type);
      }
  
      @Test
      public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParamsNoValues() {
          ResourceCleanupService.ResourceType type =
                  resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams(
                          null, null, null, null);
          Assert.assertNull(type);
      }
  
      @Test
      public void testIsVmOfferingPurgeResourcesEnabled() {
          Mockito.when(serviceOfferingDetailsDao.getDetail(1L,
                  ServiceOffering.PURGE_DB_ENTITIES_KEY)).thenReturn(null);
          Assert.assertFalse(resourceCleanupService.isVmOfferingPurgeResourcesEnabled(1L));
          Mockito.when(serviceOfferingDetailsDao.getDetail(2L,
                  ServiceOffering.PURGE_DB_ENTITIES_KEY)).thenReturn("false");
          Assert.assertFalse(resourceCleanupService.isVmOfferingPurgeResourcesEnabled(2L));
          Mockito.when(serviceOfferingDetailsDao.getDetail(3L,
                  ServiceOffering.PURGE_DB_ENTITIES_KEY)).thenReturn("true");
          Assert.assertTrue(resourceCleanupService.isVmOfferingPurgeResourcesEnabled(3L));
      }
  
      @Test
      public void testPurgeExpungedResource() {
          Assert.assertFalse(resourceCleanupService.purgeExpungedResource(1L, null));
  
          Mockito.doReturn(true).when(resourceCleanupService)
                  .purgeExpungedResource(Mockito.anyLong(), Mockito.any());
          Assert.assertTrue(resourceCleanupService.purgeExpungedResource(1L,
                  ResourceCleanupService.ResourceType.VirtualMachine));
      }
  
      @Test(expected = InvalidParameterValueException.class)
      public void testPurgeExpungedResourcesInvalidResourceType() {
          PurgeExpungedResourcesCmd cmd = Mockito.mock(PurgeExpungedResourcesCmd.class);
          Mockito.when(cmd.getResourceType()).thenReturn("Volume");
          resourceCleanupService.purgeExpungedResources(cmd);
      }
  
      @Test(expected = InvalidParameterValueException.class)
      public void testPurgeExpungedResourcesInvalidBatchSize() {
          PurgeExpungedResourcesCmd cmd = Mockito.mock(PurgeExpungedResourcesCmd.class);
          Mockito.when(cmd.getBatchSize()).thenReturn(-1L);
          resourceCleanupService.purgeExpungedResources(cmd);
      }
  
      @Test(expected = InvalidParameterValueException.class)
      public void testPurgeExpungedResourcesInvalidDates() {
          Calendar cal = Calendar.getInstance();
          Date startDate = new Date();
          cal.setTime(startDate);
          cal.add(Calendar.DATE, -1);
          Date endDate = cal.getTime();
          PurgeExpungedResourcesCmd cmd = Mockito.mock(PurgeExpungedResourcesCmd.class);
          Mockito.when(cmd.getStartDate()).thenReturn(startDate);
          Mockito.when(cmd.getEndDate()).thenReturn(endDate);
          resourceCleanupService.purgeExpungedResources(cmd);
      }
  
      @Test
      public void testPurgeExpungedResources() {
          Mockito.doReturn((long)ids.size()).when(resourceCleanupService).purgeExpungedResourceUsingJob(
                  ResourceCleanupService.ResourceType.VirtualMachine, batchSize, null, null);
          PurgeExpungedResourcesCmd cmd = Mockito.mock(PurgeExpungedResourcesCmd.class);
          Mockito.when(cmd.getResourceType()).thenReturn(ResourceCleanupService.ResourceType.VirtualMachine.toString());
          Mockito.when(cmd.getBatchSize()).thenReturn(batchSize);
          long result = resourceCleanupService.purgeExpungedResources(cmd);
          Assert.assertEquals(ids.size(), result);
      }
  
      @Test
      public void testExpungedVmResourcesLaterIfNeededFalse() {
          VirtualMachine vm = Mockito.mock(VirtualMachine.class);
          Mockito.when(vm.getServiceOfferingId()).thenReturn(1L);
          Mockito.doReturn(false).when(resourceCleanupService).isVmOfferingPurgeResourcesEnabled(1L);
          resourceCleanupService.purgeExpungedVmResourcesLaterIfNeeded(vm);
          Mockito.verify(resourceCleanupService, Mockito.never()).purgeExpungedResourceLater(Mockito.anyLong(), Mockito.any());
      }
  
      @Test
      public void testExpungedVmResourcesLaterIfNeeded() {
          VirtualMachine vm = Mockito.mock(VirtualMachine.class);
          Mockito.when(vm.getServiceOfferingId()).thenReturn(1L);
          Mockito.doReturn(true).when(resourceCleanupService).isVmOfferingPurgeResourcesEnabled(1L);
          Mockito.doNothing().when(resourceCleanupService).purgeExpungedResourceLater(Mockito.anyLong(), Mockito.any());
          resourceCleanupService.purgeExpungedVmResourcesLaterIfNeeded(vm);
          Mockito.verify(resourceCleanupService, Mockito.times(1))
                  .purgeExpungedResourceLater(Mockito.anyLong(), Mockito.any());
      }
  
      @Test
      public void testGetBatchSizeFromConfig() {
          int value = 50;
          overrideConfigValue(ResourceCleanupService.ExpungedResourcesPurgeBatchSize, String.valueOf(value));
          Assert.assertEquals(value, resourceCleanupService.getBatchSizeFromConfig());
      }
  
      @Test
      public void testGetResourceTypesFromConfigEmpty() {
          overrideConfigValue(ResourceCleanupService.ExpungedResourcePurgeResources, "");
          Assert.assertNull(resourceCleanupService.getResourceTypesFromConfig());
      }
  
      @Test
      public void testGetResourceTypesFromConfig() {
          overrideConfigValue(ResourceCleanupService.ExpungedResourcePurgeResources, "VirtualMachine");
          List<ResourceCleanupService.ResourceType> types  = resourceCleanupService.getResourceTypesFromConfig();
          Assert.assertEquals(1, types.size());
      }
  
      @Test
      public void testCalculatePastDateFromConfigNull() {
          Assert.assertNull(resourceCleanupService.calculatePastDateFromConfig(
                  ResourceCleanupService.ExpungedResourcesPurgeKeepPastDays.key(),
                  null));
          Assert.assertNull(resourceCleanupService.calculatePastDateFromConfig(
                  ResourceCleanupService.ExpungedResourcesPurgeKeepPastDays.key(),
                  0));
      }
  
      @Test(expected = CloudRuntimeException.class)
      public void testCalculatePastDateFromConfigFail() {
          Assert.assertNull(resourceCleanupService.calculatePastDateFromConfig(
                  ResourceCleanupService.ExpungedResourcesPurgeKeepPastDays.key(),
                  -1));
      }
  
      @Test
      public void testCalculatePastDateFromConfig() {
          int days = 10;
          Date result = resourceCleanupService.calculatePastDateFromConfig(
                  ResourceCleanupService.ExpungedResourcesPurgeKeepPastDays.key(),
                  days);
          Instant resultInstant = result.toInstant();
          ZoneId systemZone = ZoneId.systemDefault();
          ZoneRules rules = systemZone.getRules();
          ZonedDateTime resultDateTime = resultInstant.atZone(systemZone);
          boolean isDSTInResultDateTime = rules.isDaylightSavings(resultDateTime.toInstant());
  
          ZonedDateTime todayDateTime = ZonedDateTime.now(systemZone);
          boolean isDSTInTodayDateTime = rules.isDaylightSavings(todayDateTime.toInstant());
  
          Duration duration = Duration.between(resultDateTime, todayDateTime);
          long actualDays = TimeUnit.DAYS.convert(duration.toMillis(), TimeUnit.MILLISECONDS);
  
          if (!isDSTInResultDateTime && isDSTInTodayDateTime) {
              Assert.assertEquals(days - 1, actualDays);
          } else {
              Assert.assertEquals(days, actualDays);
          }
      }
  
      @Test
      public void testParseDateFromConfig() {
          Assert.assertNull(resourceCleanupService.parseDateFromConfig(
                  ResourceCleanupService.ExpungedResourcesPurgeStartTime.key(), ""));
          Date date = resourceCleanupService.parseDateFromConfig(
                  ResourceCleanupService.ExpungedResourcesPurgeStartTime.key(), "2020-01-01");
          Assert.assertNotNull(date);
          Calendar calendar = Calendar.getInstance();
          calendar.setTime(date);
          Assert.assertEquals(2020, calendar.get(Calendar.YEAR));
          Assert.assertEquals(0, calendar.get(Calendar.MONTH));
          Assert.assertEquals(1, calendar.get(Calendar.DATE));
      }
  
      @Test(expected = CloudRuntimeException.class)
      public void testParseDateFromConfigFail() {
          resourceCleanupService.parseDateFromConfig(
                  ResourceCleanupService.ExpungedResourcesPurgeStartTime.key(), "ABC");
      }
  }
  