// 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.backup;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
import java.util.UUID;

import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd;
import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd;
import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd;
import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd;
import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.BackupDetailsDao;
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.reservation.ReservationVO;
import org.apache.cloudstack.reservation.dao.ReservationDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.commons.lang3.StringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.test.util.ReflectionTestUtils;

import com.cloud.alert.AlertManager;
import com.cloud.api.query.dao.UserVmJoinDao;
import com.cloud.api.query.vo.UserVmJoinVO;
import com.cloud.capacity.CapacityVO;
import com.cloud.configuration.Resource;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.domain.Domain;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.network.Network;
import com.cloud.network.NetworkService;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.offering.DiskOffering;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.DomainManager;
import com.cloud.user.ResourceLimitService;
import com.cloud.user.User;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.db.DbUtil;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.vm.VMInstanceDetailVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.VmDiskInfo;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.google.gson.Gson;

@RunWith(MockitoJUnitRunner.class)
public class BackupManagerTest {
    @Spy
    @InjectMocks
    BackupManagerImpl backupManager = new BackupManagerImpl();

    @Mock
    BackupOfferingDao backupOfferingDao;

    @Mock
    BackupDetailsDao backupDetailsDao;

    @Mock
    BackupProvider backupProvider;

    @Mock
    VirtualMachineManager virtualMachineManager;

    @Mock
    VolumeApiService volumeApiService;

    @Mock
    VolumeDao volumeDao;

    @Mock
    VMInstanceDao vmInstanceDao;

    @Mock
    AccountManager accountManager;

    @Mock
    DomainManager domainManager;

    @Mock
    ResourceLimitService resourceLimitMgr;

    @Mock
    BackupScheduleDao backupScheduleDao;

    @Mock
    BackupDao backupDao;

    @Mock
    DataCenterDao dataCenterDao;

    @Mock
    private AlertManager alertManagerMock;

    @Mock
    private Domain domainMock;

    @Mock
    private VMInstanceVO vmInstanceVOMock;

    @Mock
    private CreateBackupScheduleCmd createBackupScheduleCmdMock;

    @Mock
    private BackupOfferingVO backupOfferingVOMock;

    @Mock
    private AsyncJobVO asyncJobVOMock;

    @Mock
    private BackupScheduleVO backupScheduleVOMock;

    @Mock
    private AccountVO accountVOMock;

    @Mock
    private CallContext callContextMock;

    @Mock
    private DeleteBackupScheduleCmd deleteBackupScheduleCmdMock;

    @Mock
    DiskOfferingDao diskOfferingDao;

    @Mock
    ServiceOfferingDao serviceOfferingDao;

    @Mock
    VMTemplateDao vmTemplateDao;

    @Mock
    UserVmJoinDao userVmJoinDao;

    @Mock
    PrimaryDataStoreDao primaryDataStoreDao;

    @Mock
    HostDao hostDao;

    @Mock
    private NetworkDao networkDao;

    @Mock
    private NetworkService networkService;

    @Mock
    private VMInstanceDetailsDao vmInstanceDetailsDao;

    @Mock
    AccountDao accountDao;

    @Mock
    DomainDao domainDao;

    @Mock
    ReservationDao reservationDao;

    @Mock
    private GuestOSDao _guestOSDao;

    private Gson gson;

    private String[] hostPossibleValues = {"127.0.0.1", "hostname"};
    private String[] datastoresPossibleValues = {"e9804933-8609-4de3-bccc-6278072a496c", "datastore-name"};
    private AutoCloseable closeable;
    private ConfigDepotImpl configDepotImpl;
    private boolean updatedConfigKeyDepot = false;
    private MockedStatic<DbUtil> dbUtilMockedStatic;
    private final List<String> mockedGlobalLocks = new ArrayList<>();

    @Before
    public void setup() throws Exception {
        gson = new Gson();

        closeable = MockitoAnnotations.openMocks(this);
        when(backupOfferingDao.findById(null)).thenReturn(null);
        when(backupOfferingDao.findById(123l)).thenReturn(null);

        BackupOfferingVO offering = Mockito.spy(BackupOfferingVO.class);
        when(offering.getName()).thenCallRealMethod();
        when(offering.getDescription()).thenCallRealMethod();
        when(offering.isUserDrivenBackupAllowed()).thenCallRealMethod();

        BackupOfferingVO offeringUpdate = Mockito.spy(BackupOfferingVO.class);

        when(backupOfferingDao.findById(1234l)).thenReturn(offering);
        when(backupOfferingDao.createForUpdate(1234l)).thenReturn(offeringUpdate);
        when(backupOfferingDao.update(1234l, offeringUpdate)).thenAnswer(answer -> {
            offering.setName("New name");
            offering.setDescription("New description");
            offering.setUserDrivenBackupAllowed(true);
            return true;
        });

        backupProvider = mock(BackupProvider.class);
        when(backupProvider.getName()).thenReturn("testbackupprovider");
        Map<String, BackupProvider> backupProvidersMap = new HashMap<>();
        backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider);
        ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap);

        when(reservationDao.persist(any(ReservationVO.class)))
                .thenAnswer((Answer<ReservationVO>) invocation -> {
                    ReservationVO reservationVO = (ReservationVO)invocation.getArguments()[0];
                    ReflectionTestUtils.setField(reservationVO, "id", 10L);
                    return reservationVO;
                });
        dbUtilMockedStatic = Mockito.mockStatic(DbUtil.class);
        dbUtilMockedStatic.when(() -> DbUtil.getGlobalLock(anyString(), anyInt())).thenAnswer((Answer<Boolean>) invocation -> {
                String lockName = invocation.getArgument(0);
                if (!StringUtils.isBlank(lockName) && !mockedGlobalLocks.contains(lockName)) {
                    mockedGlobalLocks.add(lockName);
                    return true;
                }
                return false;
            });
        dbUtilMockedStatic.when(() -> DbUtil.releaseGlobalLock(anyString())).thenAnswer((Answer<Boolean>) invocation -> {
            String lockName = invocation.getArgument(0);
            if (!StringUtils.isBlank(lockName)) {
                mockedGlobalLocks.remove(lockName);
            }
            return true;
        });

        Account account = mock(Account.class);
        User user = mock(User.class);
        CallContext.register(user, account);
    }

    @After
    public void tearDown() throws Exception {
        dbUtilMockedStatic.close();
        closeable.close();
        if (updatedConfigKeyDepot) {
            ReflectionTestUtils.setField(BackupManager.BackupFrameworkEnabled, "s_depot", configDepotImpl);
        }
        CallContext.unregister();
    }

    private void overrideBackupFrameworkConfigValue() {
        ConfigKey configKey = BackupManager.BackupFrameworkEnabled;
        this.configDepotImpl = (ConfigDepotImpl) ReflectionTestUtils.getField(configKey, "s_depot");
        ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class);
        Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()),
                Mockito.eq(ConfigKey.Scope.Global), Mockito.isNull())).thenReturn("true");
        Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()),
                Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("true");
        Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupProviderPlugin.key()),
                Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("testbackupprovider");
        ReflectionTestUtils.setField(configKey, "s_depot", configDepot);
        updatedConfigKeyDepot = true;
    }

    @Test
    public void testExceptionWhenUpdateWithNullId() {
        try {
            Long id = null;

            UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class);
            when(cmd.getId()).thenReturn(id);

            backupManager.updateBackupOffering(cmd);
        } catch (InvalidParameterValueException e) {
            assertEquals("Unable to find Backup Offering with id: [null].", e.getMessage());
        }
    }

    @Test(expected = InvalidParameterValueException.class)
    public void testExceptionWhenUpdateWithNonExistentId() {
        Long id = 123l;

        UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class);
        when(cmd.getId()).thenReturn(id);

        backupManager.updateBackupOffering(cmd);
    }

    @Test(expected = ServerApiException.class)
    public void testExceptionWhenUpdateWithoutChanges() {
        UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class);
        when(cmd.getName()).thenReturn(null);
        when(cmd.getDescription()).thenReturn(null);
        when(cmd.getAllowUserDrivenBackups()).thenReturn(null);

        Mockito.doCallRealMethod().when(cmd).execute();

        cmd.execute();
    }

    @Test
    public void testUpdateBackupOfferingSuccess() {
        Long id = 1234l;

        UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class);
        when(cmd.getId()).thenReturn(id);
        when(cmd.getName()).thenReturn("New name");
        when(cmd.getDescription()).thenReturn("New description");
        when(cmd.getAllowUserDrivenBackups()).thenReturn(true);

        BackupOffering updated = backupManager.updateBackupOffering(cmd);
        assertEquals("New name", updated.getName());
        assertEquals("New description", updated.getDescription());
        assertEquals(true, updated.isUserDrivenBackupAllowed());
    }

    @Test
    public void restoreBackedUpVolumeTestHostIpAndDatastoreUuid() {
        BackupVO backupVO = new BackupVO();
        VMInstanceVO vm = mock(VMInstanceVO.class);
        String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f";
        String vmName = "i-2-3-VM";
        VirtualMachine.State vmState = VirtualMachine.State.Running;
        Mockito.when(vm.getName()).thenReturn(vmName);
        Mockito.when(vm.getState()).thenReturn(vmState);
        Pair<String, VirtualMachine.State> vmNameAndState = new Pair<>(vmName, vmState);

        Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class);
        when(volumeInfo.getUuid()).thenReturn(volumeUuid);

        doReturn(new Pair<Boolean, String>(Boolean.TRUE, "Success"))
            .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class),
                any(String.class), any(String.class), any(Pair.class));

        Pair<Boolean, String> restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm);

        assertEquals(Boolean.TRUE, restoreBackedUpVolume.first());
        assertEquals("Success", restoreBackedUpVolume.second());

        verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class),
            any(String.class), any(String.class), any(Pair.class));
    }

    @Test
    public void restoreBackedUpVolumeTestHostIpAndDatastoreName() {
        BackupVO backupVO = new BackupVO();
        VMInstanceVO vm = mock(VMInstanceVO.class);
        String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f";
        String vmName = "i-2-3-VM";
        VirtualMachine.State vmState = VirtualMachine.State.Running;
        Mockito.when(vm.getName()).thenReturn(vmName);
        Mockito.when(vm.getState()).thenReturn(vmState);
        Pair<String, VirtualMachine.State> vmNameAndState = new Pair<>(vmName, vmState);

        Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class);
        when(volumeInfo.getUuid()).thenReturn(volumeUuid);

        doReturn(new Pair<Boolean, String>(Boolean.TRUE, "Success2"))
            .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class),
                any(String.class), any(String.class), any(Pair.class));

        Pair<Boolean, String> restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm);

        assertEquals(Boolean.TRUE, restoreBackedUpVolume.first());
        assertEquals("Success2", restoreBackedUpVolume.second());

        verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class),
            any(String.class), any(String.class), any(Pair.class));
    }

    @Test
    public void restoreBackedUpVolumeTestHostNameAndDatastoreUuid() {
        BackupVO backupVO = new BackupVO();
        VMInstanceVO vm = mock(VMInstanceVO.class);
        String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f";
        String vmName = "i-2-3-VM";
        VirtualMachine.State vmState = VirtualMachine.State.Running;
        Mockito.when(vm.getName()).thenReturn(vmName);
        Mockito.when(vm.getState()).thenReturn(vmState);
        Pair<String, VirtualMachine.State> vmNameAndState = new Pair<>(vmName, vmState);

        Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class);
        when(volumeInfo.getUuid()).thenReturn(volumeUuid);

        doReturn(new Pair<Boolean, String>(Boolean.TRUE, "Success3"))
            .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class),
                any(String.class), any(String.class), any(Pair.class));

        Pair<Boolean, String> restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm);

        assertEquals(Boolean.TRUE, restoreBackedUpVolume.first());
        assertEquals("Success3", restoreBackedUpVolume.second());

        verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class),
            any(String.class), any(String.class), any(Pair.class));
    }

    @Test
    public void restoreBackedUpVolumeTestHostAndDatastoreName() {
        BackupVO backupVO = new BackupVO();
        VMInstanceVO vm = mock(VMInstanceVO.class);
        String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f";
        String vmName = "i-2-3-VM";
        VirtualMachine.State vmState = VirtualMachine.State.Running;
        Mockito.when(vm.getName()).thenReturn(vmName);
        Mockito.when(vm.getState()).thenReturn(vmState);
        Pair<String, VirtualMachine.State> vmNameAndState = new Pair<>(vmName, vmState);

        Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class);
        when(volumeInfo.getUuid()).thenReturn(volumeUuid);

        doReturn(new Pair<Boolean, String>(Boolean.TRUE, "Success4"))
            .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class),
                any(String.class), any(String.class), any(Pair.class));

        Pair<Boolean, String> restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm);

        assertEquals(Boolean.TRUE, restoreBackedUpVolume.first());
        assertEquals("Success4", restoreBackedUpVolume.second());

        verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class),
            any(String.class), any(String.class), any(Pair.class));
    }

    @Test
    public void tryRestoreVMTestRestoreSucceeded() throws NoTransitionException {
        BackupOffering offering = mock(BackupOffering.class);
        VolumeVO volumeVO = mock(VolumeVO.class);
        VMInstanceVO vm = mock(VMInstanceVO.class);
        BackupVO backup = mock(BackupVO.class);

        try (MockedStatic<ActionEventUtils> utils = Mockito.mockStatic(ActionEventUtils.class)) {
            Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
                    Mockito.eq(true), Mockito.eq(0))).thenReturn(1L);
            Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.eq(0))).thenReturn(2L);

            Mockito.when(volumeDao.findIncludingRemovedByInstanceAndType(1L, null)).thenReturn(Collections.singletonList(volumeVO));
            Mockito.when(virtualMachineManager.stateTransitTo(Mockito.eq(vm), Mockito.eq(VirtualMachine.Event.RestoringRequested), Mockito.any())).thenReturn(true);
            Mockito.when(volumeApiService.stateTransitTo(Mockito.eq(volumeVO), Mockito.eq(Volume.Event.RestoreRequested))).thenReturn(true);

            Mockito.when(vm.getId()).thenReturn(1L);
            Mockito.when(offering.getProvider()).thenReturn("veeam");
            Mockito.doReturn(backupProvider).when(backupManager).getBackupProvider("veeam");
            Mockito.when(backupProvider.restoreVMFromBackup(vm, backup)).thenReturn(true);

            backupManager.tryRestoreVM(backup, vm, offering, "Nothing to write here.");
        }
    }

    @Test
    public void tryRestoreVMTestRestoreFails() throws NoTransitionException {
        BackupOffering offering = mock(BackupOffering.class);
        VolumeVO volumeVO = mock(VolumeVO.class);
        VMInstanceVO vm = mock(VMInstanceVO.class);
        BackupVO backup = mock(BackupVO.class);

        try (MockedStatic<ActionEventUtils> utils = Mockito.mockStatic(ActionEventUtils.class)) {
            Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
                    Mockito.eq(true), Mockito.eq(0))).thenReturn(1L);
            Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.eq(0))).thenReturn(2L);

            Mockito.when(volumeDao.findIncludingRemovedByInstanceAndType(1L, null)).thenReturn(Collections.singletonList(volumeVO));
            Mockito.when(virtualMachineManager.stateTransitTo(Mockito.eq(vm), Mockito.eq(VirtualMachine.Event.RestoringRequested), Mockito.any())).thenReturn(true);
            Mockito.when(volumeApiService.stateTransitTo(Mockito.eq(volumeVO), Mockito.eq(Volume.Event.RestoreRequested))).thenReturn(true);
            Mockito.when(virtualMachineManager.stateTransitTo(Mockito.eq(vm), Mockito.eq(VirtualMachine.Event.RestoringFailed), Mockito.any())).thenReturn(true);
            Mockito.when(volumeApiService.stateTransitTo(Mockito.eq(volumeVO), Mockito.eq(Volume.Event.RestoreFailed))).thenReturn(true);

            Mockito.when(vm.getId()).thenReturn(1L);
            Mockito.when(offering.getProvider()).thenReturn("veeam");
            Mockito.doReturn(backupProvider).when(backupManager).getBackupProvider("veeam");
            Mockito.when(backupProvider.restoreVMFromBackup(vm, backup)).thenReturn(false);
            try {
                backupManager.tryRestoreVM(backup, vm, offering, "Checking message error.");
                fail("An exception is needed.");
            } catch (CloudRuntimeException e) {
                assertEquals("Error restoring VM from backup [Checking message error.].", e.getMessage());
            }
        }
    }

    @Test
    public void testConfigureBackupSchedule() {
        Long vmId = 1L;
        Long zoneId = 2L;
        Long accountId = 3L;
        Long domainId = 4L;
        Long backupOfferingId = 5L;

        CreateBackupScheduleCmd cmd = Mockito.mock(CreateBackupScheduleCmd.class);
        when(cmd.getVmId()).thenReturn(vmId);
        when(cmd.getTimezone()).thenReturn("GMT");
        when(cmd.getIntervalType()).thenReturn(DateUtil.IntervalType.DAILY);
        when(cmd.getMaxBackups()).thenReturn(8);
        when(cmd.getSchedule()).thenReturn("00:00:00");
        when(cmd.getQuiesceVM()).thenReturn(null);

        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
        when(vmInstanceDao.findById(vmId)).thenReturn(vm);
        when(vm.getDataCenterId()).thenReturn(zoneId);
        when(vm.getAccountId()).thenReturn(accountId);
        when(vm.getBackupOfferingId()).thenReturn(backupOfferingId);

        overrideBackupFrameworkConfigValue();

        Account account = Mockito.mock(Account.class);
        when(accountManager.getAccount(accountId)).thenReturn(account);
        when(account.getDomainId()).thenReturn(domainId);
        Domain domain = Mockito.mock(Domain.class);
        when(domainManager.getDomain(domainId)).thenReturn(domain);
        when(resourceLimitMgr.findCorrectResourceLimitForAccount(account, Resource.ResourceType.backup, null)).thenReturn(8L);
        when(resourceLimitMgr.findCorrectResourceLimitForDomain(domain, Resource.ResourceType.backup, null)).thenReturn(8L);

        BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class);
        when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering);
        when(offering.isUserDrivenBackupAllowed()).thenReturn(true);
        when(offering.getProvider()).thenReturn("test");

        BackupScheduleVO schedule = mock(BackupScheduleVO.class);
        when(backupScheduleDao.findByVMAndIntervalType(vmId, DateUtil.IntervalType.DAILY)).thenReturn(schedule);

        backupManager.configureBackupSchedule(cmd);

        verify(schedule, times(1)).setScheduleType((short) DateUtil.IntervalType.DAILY.ordinal());
        verify(schedule, times(1)).setSchedule("00:00:00");
        verify(schedule, times(1)).setTimezone(TimeZone.getTimeZone("GMT").getID());
        verify(schedule, times(1)).setMaxBackups(8);
    }

    @Test
    public void configureBackupScheduleTestEnsureLimitCheckIsPerformed() {
        long vmId = 1L;
        long zoneId = 2L;
        long accountId = 3L;
        long domainId = 4L;
        long backupOfferingId = 5L;

        when(createBackupScheduleCmdMock.getVmId()).thenReturn(vmId);
        when(createBackupScheduleCmdMock.getTimezone()).thenReturn("GMT");
        when(createBackupScheduleCmdMock.getIntervalType()).thenReturn(DateUtil.IntervalType.DAILY);
        when(createBackupScheduleCmdMock.getMaxBackups()).thenReturn(8);

        when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock);
        when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId);
        when(vmInstanceVOMock.getAccountId()).thenReturn(accountId);
        when(vmInstanceVOMock.getBackupOfferingId()).thenReturn(backupOfferingId);

        when(backupOfferingDao.findById(backupOfferingId)).thenReturn(backupOfferingVOMock);
        when(backupOfferingVOMock.isUserDrivenBackupAllowed()).thenReturn(true);

        overrideBackupFrameworkConfigValue();

        when(accountManager.getAccount(accountId)).thenReturn(accountVOMock);
        when(accountVOMock.getDomainId()).thenReturn(domainId);
        when(domainManager.getDomain(domainId)).thenReturn(domainMock);
        when(resourceLimitMgr.findCorrectResourceLimitForAccount(accountVOMock, Resource.ResourceType.backup, null)).thenReturn(10L);
        when(resourceLimitMgr.findCorrectResourceLimitForDomain(domainMock, Resource.ResourceType.backup, null)).thenReturn(1L);

        InvalidParameterValueException exception = Assert.assertThrows(InvalidParameterValueException.class,
                () -> backupManager.configureBackupSchedule(createBackupScheduleCmdMock));
        Assert.assertEquals("'maxbackups' should not exceed the domain/account backup limit.", exception.getMessage());
    }

    @Test
    public void createBackupTestCreateScheduledBackup() throws ResourceAllocationException {
        Long vmId = 1L;
        Long zoneId = 2L;
        Long scheduleId = 3L;
        Long backupOfferingId = 4L;
        Long accountId = 5L;
        Long backupId = 6L;
        Long oldestBackupId = 7L;
        Long newBackupSize = 1000000000L;
        Long oldBackupSize = 400000000L;
        long domainId = 101L;

        when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock);
        when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId);
        when(vmInstanceVOMock.getBackupOfferingId()).thenReturn(backupOfferingId);
        when(vmInstanceVOMock.getAccountId()).thenReturn(accountId);

        overrideBackupFrameworkConfigValue();
        when(backupOfferingDao.findById(backupOfferingId)).thenReturn(backupOfferingVOMock);
        when(backupOfferingVOMock.isUserDrivenBackupAllowed()).thenReturn(true);
        when(backupOfferingVOMock.getProvider()).thenReturn("testbackupprovider");

        Mockito.doReturn(scheduleId).when(backupManager).getBackupScheduleId(asyncJobVOMock);

        when(accountManager.getAccount(accountId)).thenReturn(accountVOMock);
        when(accountVOMock.getDomainId()).thenReturn(domainId);

        BackupScheduleVO schedule = mock(BackupScheduleVO.class);
        when(backupScheduleDao.findById(scheduleId)).thenReturn(schedule);
        when(schedule.getMaxBackups()).thenReturn(2);

        VolumeVO volume = mock(VolumeVO.class);
        when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume));
        when(volume.getState()).thenReturn(Volume.State.Ready);
        when(volumeApiService.getVolumePhysicalSize(null, null, null)).thenReturn(newBackupSize);

        BackupProvider backupProvider = mock(BackupProvider.class);
        Backup backup = mock(Backup.class);
        when(backup.getId()).thenReturn(backupId);
        when(backup.getSize()).thenReturn(newBackupSize);
        when(backupProvider.getName()).thenReturn("testbackupprovider");
        when(backupProvider.takeBackup(vmInstanceVOMock, null)).thenReturn(new Pair<>(true, backup));
        Map<String, BackupProvider> backupProvidersMap = new HashMap<>();
        backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider);
        ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap);

        BackupVO backupVO = mock(BackupVO.class);
        when(backupVO.getId()).thenReturn(backupId);
        BackupVO oldestBackupVO = mock(BackupVO.class);

        when(backupDao.findById(backupId)).thenReturn(backupVO);
        List<BackupVO> backups = new ArrayList<>(List.of(oldestBackupVO));
        when(backupDao.listBySchedule(scheduleId)).thenReturn(backups);

        CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class);
        when(cmd.getVmId()).thenReturn(vmId);
        when(cmd.getName()).thenReturn("new-backup1");
        when(cmd.getQuiesceVM()).thenReturn(null);

        try (MockedStatic<ActionEventUtils> ignored = Mockito.mockStatic(ActionEventUtils.class)) {
            Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(),
                    Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyString(),
                    Mockito.anyLong(), Mockito.anyString())).thenReturn(1L);

            assertTrue(backupManager.createBackup(cmd, asyncJobVOMock));

            Mockito.verify(resourceLimitMgr, times(1))
                    .checkResourceLimitWithTag(accountVOMock, domainId, true, Resource.ResourceType.backup, null, 1L);
            Mockito.verify(resourceLimitMgr, times(1))
                    .checkResourceLimitWithTag(accountVOMock, domainId, true, Resource.ResourceType.backup_storage, null, newBackupSize);

            Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup);
            Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, newBackupSize);
            Mockito.verify(backupDao, times(1)).update(backupVO.getId(), backupVO);
            Mockito.verify(backupManager, times(1)).deleteOldestBackupFromScheduleIfRequired(vmId, scheduleId);
        }
    }

    @Test(expected = ResourceAllocationException.class)
    public void createBackupTestResourceLimitReached() throws ResourceAllocationException {
        Long vmId = 1L;
        Long zoneId = 2L;
        Long scheduleId = 3L;
        Long backupOfferingId = 4L;
        Long accountId = 5L;
        long domainId = 101L;

        when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock);
        when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId);
        when(vmInstanceVOMock.getBackupOfferingId()).thenReturn(backupOfferingId);
        when(vmInstanceVOMock.getAccountId()).thenReturn(accountId);

        overrideBackupFrameworkConfigValue();
        BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class);
        when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering);
        when(offering.isUserDrivenBackupAllowed()).thenReturn(true);
        when(offering.getProvider()).thenReturn("testbackupprovider");

        Account account = Mockito.mock(Account.class);
        when(accountManager.getAccount(accountId)).thenReturn(account);
        when(account.getDomainId()).thenReturn(domainId);
        Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup)).when(resourceLimitMgr)
                .checkResourceLimitWithTag(account, domainId, true, Resource.ResourceType.backup, null, 1L);

        CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class);
        when(cmd.getVmId()).thenReturn(vmId);
        when(cmd.getQuiesceVM()).thenReturn(null);

        String jobParams = "{}";
        when(asyncJobVOMock.getCmdInfo()).thenReturn(jobParams);
        when(asyncJobVOMock.getId()).thenReturn(1L);

        backupManager.createBackup(cmd, asyncJobVOMock);

        String msg = "Backup storage space resource limit exceeded for account id : " + accountId + ". Failed to create backup";
        Mockito.verify(alertManagerMock, times(1)).sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Backup resource limit exceeded for account id : " + accountId
                + ". Failed to create backups; please use updateResourceLimit to increase the limit");
    }

    @Test (expected = ResourceAllocationException.class)
    public void testCreateBackupStorageLimitReached() throws ResourceAllocationException {
        Long vmId = 1L;
        Long zoneId = 2L;
        Long scheduleId = 3L;
        Long backupOfferingId = 4L;
        Long accountId = 5L;
        long domainId = 101L;
        long size = 10000L;

        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
        when(vmInstanceDao.findById(vmId)).thenReturn(vm);
        when(vm.getDataCenterId()).thenReturn(zoneId);
        when(vm.getBackupOfferingId()).thenReturn(backupOfferingId);
        when(vm.getAccountId()).thenReturn(accountId);

        VolumeVO volume = mock(VolumeVO.class);
        when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume));
        when(volume.getState()).thenReturn(Volume.State.Ready);
        when(volumeApiService.getVolumePhysicalSize(null, null, null)).thenReturn(size);

        overrideBackupFrameworkConfigValue();
        BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class);
        when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering);
        when(offering.isUserDrivenBackupAllowed()).thenReturn(true);
        when(offering.getProvider()).thenReturn("testbackupprovider");

        Mockito.doReturn(scheduleId).when(backupManager).getBackupScheduleId(asyncJobVOMock);

        Account account = Mockito.mock(Account.class);
        when(accountManager.getAccount(accountId)).thenReturn(account);
        when(account.getDomainId()).thenReturn(domainId);
        Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup_storage))
                .when(resourceLimitMgr)
                .checkResourceLimitWithTag(account, domainId, true, Resource.ResourceType.backup_storage, null, size);

        CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class);
        when(cmd.getVmId()).thenReturn(vmId);
        when(cmd.getQuiesceVM()).thenReturn(null);

        backupManager.createBackup(cmd, asyncJobVOMock);

        String msg = "Backup storage space resource limit exceeded for account id : " + accountId + ". Failed to create backup";
        Mockito.verify(alertManagerMock, times(1)).sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Backup storage space resource limit exceeded for account id : " + accountId
                + ". Failed to create backups; please use updateResourceLimit to increase the limit");
    }

    @Test
    public void testBackupSyncTask() {
        Long dataCenterId = 1L;
        Long vmId = 2L;
        Long accountId = 3L;
        Long backup2Id = 4L;
        String restorePoint1ExternalId = "1234";
        Long backup1Size = 1 * Resource.ResourceType.bytesToGiB;
        Long backup2Size = 2 * Resource.ResourceType.bytesToGiB;
        Long newBackupSize = 3 * Resource.ResourceType.bytesToGiB;
        Long restorePointSize = 4 * Resource.ResourceType.bytesToGiB;

        overrideBackupFrameworkConfigValue();

        DataCenterVO dataCenter = mock(DataCenterVO.class);
        when(dataCenter.getId()).thenReturn(dataCenterId);
        when(dataCenterDao.listAllZones()).thenReturn(List.of(dataCenter));

        BackupProvider backupProvider = mock(BackupProvider.class);
        when(backupProvider.getName()).thenReturn("testbackupprovider");
        backupManager.setBackupProviders(List.of(backupProvider));
        backupManager.start();

        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
        when(vm.getId()).thenReturn(vmId);
        when(vm.getAccountId()).thenReturn(accountId);
        List<Long> vmIds = List.of(vmId);
        when(backupDao.listVmIdsWithBackupsInZone(dataCenterId)).thenReturn(vmIds);
        when(vmInstanceDao.listByZoneAndBackupOffering(dataCenterId, null)).thenReturn(List.of(vm));

        Backup.RestorePoint restorePoint1 = new Backup.RestorePoint(restorePoint1ExternalId, DateUtil.now(), "Full", restorePointSize, 0L);
        Backup.RestorePoint restorePoint2 = new Backup.RestorePoint("12345", DateUtil.now(), "Full", restorePointSize, 0L);
        List<Backup.RestorePoint> restorePoints = new ArrayList<>(List.of(restorePoint1, restorePoint2));
        when(backupProvider.listRestorePoints(vm)).thenReturn(restorePoints);

        BackupVO backupInDb1 = new BackupVO();
        backupInDb1.setSize(backup1Size);
        backupInDb1.setAccountId(accountId);
        backupInDb1.setExternalId(restorePoint1ExternalId);

        BackupVO backupInDb2 = new BackupVO();
        backupInDb2.setSize(backup2Size);
        backupInDb2.setAccountId(accountId);
        backupInDb2.setExternalId(null);
        ReflectionTestUtils.setField(backupInDb2, "id", backup2Id);
        when(backupDao.findById(backup2Id)).thenReturn(backupInDb2);

        when(backupDao.listByVmId(null, vmId)).thenReturn(List.of(backupInDb1, backupInDb2));

        BackupVO newBackupEntry = new BackupVO();
        newBackupEntry.setSize(newBackupSize);
        when(backupProvider.createNewBackupEntryForRestorePoint(restorePoint2, vm)).thenReturn(newBackupEntry);

        try (MockedStatic<ActionEventUtils> ignored = Mockito.mockStatic(ActionEventUtils.class)) {
            Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(),
                    Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyString(),
                    Mockito.anyLong(), Mockito.anyString())).thenReturn(1L);

            try (MockedStatic<UsageEventUtils> ignored2 = Mockito.mockStatic(UsageEventUtils.class)) {

                BackupManagerImpl.BackupSyncTask backupSyncTask = backupManager.new BackupSyncTask(backupManager);
                backupSyncTask.runInContext();

                verify(resourceLimitMgr, times(1)).decrementResourceCount(accountId, Resource.ResourceType.backup_storage, backup1Size);
                verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, restorePointSize);
                Assert.assertEquals(backupInDb1.getSize(), restorePointSize);

                verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup);
                verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, newBackupSize);

                verify(resourceLimitMgr, times(1)).decrementResourceCount(accountId, Resource.ResourceType.backup);
                verify(resourceLimitMgr, times(1)).decrementResourceCount(accountId, Resource.ResourceType.backup_storage, backup2Size);
            }
        }
    }

    @Test
    public void checkCallerAccessToBackupScheduleVmTestExecuteAccessCheckMethods() {
        long vmId = 1L;
        long dataCenterId = 2L;

        try (MockedStatic<CallContext> mockedCallContext = Mockito.mockStatic(CallContext.class)) {
            Mockito.when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock);
            Mockito.when(vmInstanceVOMock.getDataCenterId()).thenReturn(dataCenterId);
            Mockito.when(backupManager.isDisabled(dataCenterId)).thenReturn(false);

            mockedCallContext.when(CallContext::current).thenReturn(callContextMock);
            Mockito.when(callContextMock.getCallingAccount()).thenReturn(accountVOMock);
            Mockito.doNothing().when(accountManager).checkAccess(accountVOMock, null, true, vmInstanceVOMock);
            backupManager.checkCallerAccessToBackupScheduleVm(vmId);

            verify(accountManager, times(1)).checkAccess(accountVOMock, null, true, vmInstanceVOMock);
        }
    }

    @Test
    public void deleteAllVmBackupSchedulesTestReturnSuccessWhenAllSchedulesAreDeleted() {
        long vmId = 1L;
        List<BackupScheduleVO> backupSchedules = List.of(Mockito.mock(BackupScheduleVO.class), Mockito.mock(BackupScheduleVO.class));
        Mockito.when(backupScheduleDao.listByVM(vmId)).thenReturn(backupSchedules);
        Mockito.when(backupSchedules.get(0).getId()).thenReturn(2L);
        Mockito.when(backupSchedules.get(1).getId()).thenReturn(3L);
        Mockito.when(backupScheduleDao.remove(Mockito.anyLong())).thenReturn(true);

        boolean success = backupManager.deleteAllVmBackupSchedules(vmId);
        assertTrue(success);
        Mockito.verify(backupScheduleDao, times(2)).remove(Mockito.anyLong());
    }

    @Test
    public void deleteAllVmBackupSchedulesTestReturnFalseWhenAnyDeletionFails() {
        long vmId = 1L;
        List<BackupScheduleVO> backupSchedules = List.of(Mockito.mock(BackupScheduleVO.class), Mockito.mock(BackupScheduleVO.class));
        Mockito.when(backupScheduleDao.listByVM(vmId)).thenReturn(backupSchedules);
        Mockito.when(backupSchedules.get(0).getId()).thenReturn(2L);
        Mockito.when(backupSchedules.get(1).getId()).thenReturn(3L);
        Mockito.when(backupScheduleDao.remove(2L)).thenReturn(true);
        Mockito.when(backupScheduleDao.remove(3L)).thenReturn(false);

        boolean success = backupManager.deleteAllVmBackupSchedules(vmId);
        assertFalse(success);
        Mockito.verify(backupScheduleDao, times(2)).remove(Mockito.anyLong());
    }

    @Test(expected = InvalidParameterValueException.class)
    public void deleteBackupScheduleTestThrowExceptionWhenVmIdAndScheduleIdAreNull() {
        when(deleteBackupScheduleCmdMock.getVmId()).thenReturn(null);
        when(deleteBackupScheduleCmdMock.getId()).thenReturn(null);

        backupManager.deleteBackupSchedule(deleteBackupScheduleCmdMock);
    }

    @Test
    public void deleteBackupScheduleTestDeleteVmSchedulesWhenVmIdIsSpecified() {
        long vmId = 1L;

        when(deleteBackupScheduleCmdMock.getId()).thenReturn(null);
        when(deleteBackupScheduleCmdMock.getVmId()).thenReturn(vmId);
        Mockito.doNothing().when(backupManager).checkCallerAccessToBackupScheduleVm(vmId);
        Mockito.doReturn(true).when(backupManager).deleteAllVmBackupSchedules(vmId);

        boolean success = backupManager.deleteBackupSchedule(deleteBackupScheduleCmdMock);
        assertTrue(success);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void deleteBackupScheduleTestThrowExceptionWhenSpecificScheduleIsNotFound() {
        long id = 1L;
        when(deleteBackupScheduleCmdMock.getId()).thenReturn(id);
        backupManager.deleteBackupSchedule(deleteBackupScheduleCmdMock);
    }

    @Test
    public void deleteBackupScheduleTestDeleteSpecificScheduleWhenItsIdIsSpecified() {
        long id = 1L;
        long vmId = 2L;
        when(deleteBackupScheduleCmdMock.getId()).thenReturn(id);
        when(deleteBackupScheduleCmdMock.getVmId()).thenReturn(null);
        when(backupScheduleDao.findById(id)).thenReturn(backupScheduleVOMock);
        when(backupScheduleVOMock.getVmId()).thenReturn(vmId);
        Mockito.doNothing().when(backupManager).checkCallerAccessToBackupScheduleVm(vmId);
        when(backupScheduleVOMock.getId()).thenReturn(id);
        when(backupScheduleDao.remove(id)).thenReturn(true);

        boolean success = backupManager.deleteBackupSchedule(deleteBackupScheduleCmdMock);
        assertTrue(success);
    }

    @Test
    public void testGetBackupDetailsFromVM() {
        Long vmId = 1L;
        VirtualMachine vm = mock(VirtualMachine.class);
        when(vm.getServiceOfferingId()).thenReturn(1L);
        when(vm.getTemplateId()).thenReturn(2L);
        when(vm.getId()).thenReturn(vmId);

        ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class);
        when(serviceOffering.getUuid()).thenReturn("service-offering-uuid");
        when(serviceOfferingDao.findById(1L)).thenReturn(serviceOffering);
        VMTemplateVO template = mock(VMTemplateVO.class);
        when(template.getUuid()).thenReturn("template-uuid");
        when(vmTemplateDao.findById(2L)).thenReturn(template);

        VMInstanceDetailVO vmInstanceDetail = mock(VMInstanceDetailVO.class);
        when(vmInstanceDetail.getName()).thenReturn("mocked-detail-name");
        when(vmInstanceDetail.getValue()).thenReturn("mocked-detail-value");
        List<VMInstanceDetailVO> vmDetails = Collections.singletonList(vmInstanceDetail);
        when(vmInstanceDetailsDao.listDetails(vmId)).thenReturn(vmDetails);

        UserVmJoinVO userVmJoinVO = mock(UserVmJoinVO.class);
        when(userVmJoinVO.getNetworkUuid()).thenReturn("mocked-network-uuid");
        List<UserVmJoinVO> userVmJoinVOs = Collections.singletonList(userVmJoinVO);
        when(userVmJoinDao.searchByIds(vmId)).thenReturn(userVmJoinVOs);

        Map<String, String> details = backupManager.getBackupDetailsFromVM(vm);

        assertEquals("service-offering-uuid", details.get(ApiConstants.SERVICE_OFFERING_ID));
        assertEquals("[{\"networkid\":\"mocked-network-uuid\"}]", details.get(ApiConstants.NICS));
        assertEquals("{\"mocked-detail-name\":\"mocked-detail-value\"}", details.get(ApiConstants.VM_SETTINGS));
    }

    @Test
    public void getDataDiskInfoListFromBackup() {
        Long size1 = 5L * 1024 * 1024 * 1024;
        Long size2 = 10L * 1024 * 1024 * 1024;
        Backup backup = mock(Backup.class);

        Backup.VolumeInfo volumeInfo0 = mock(Backup.VolumeInfo.class);
        when(volumeInfo0.getType()).thenReturn(Volume.Type.ROOT);
        Backup.VolumeInfo volumeInfo1 = mock(Backup.VolumeInfo.class);
        when(volumeInfo1.getDiskOfferingId()).thenReturn("disk-offering-uuid-1");
        when(volumeInfo1.getSize()).thenReturn(size1);
        when(volumeInfo1.getMinIops()).thenReturn(100L);
        when(volumeInfo1.getMaxIops()).thenReturn(300L);
        when(volumeInfo1.getType()).thenReturn(Volume.Type.DATADISK);
        when(volumeInfo1.getDeviceId()).thenReturn(1L);
        Backup.VolumeInfo volumeInfo2 = mock(Backup.VolumeInfo.class);
        when(volumeInfo2.getDiskOfferingId()).thenReturn("disk-offering-uuid-2");
        when(volumeInfo2.getSize()).thenReturn(size2);
        when(volumeInfo2.getMinIops()).thenReturn(200L);
        when(volumeInfo2.getMaxIops()).thenReturn(400L);
        when(volumeInfo2.getType()).thenReturn(Volume.Type.DATADISK);
        when(volumeInfo2.getDeviceId()).thenReturn(2L);
        when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo0, volumeInfo1, volumeInfo2));

        DiskOfferingVO diskOffering1 = mock(DiskOfferingVO.class);
        when(diskOffering1.getUuid()).thenReturn("disk-offering-uuid-1");
        when(diskOffering1.getState()).thenReturn(DiskOffering.State.Active);

        DiskOfferingVO diskOffering2 = mock(DiskOfferingVO.class);
        when(diskOffering2.getUuid()).thenReturn("disk-offering-uuid-2");
        when(diskOffering2.getState()).thenReturn(DiskOffering.State.Active);

        when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering1);
        when(diskOfferingDao.findByUuid("disk-offering-uuid-2")).thenReturn(diskOffering2);

        List<VmDiskInfo> vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup);

        assertEquals(2, vmDiskInfoList.size());
        assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid());
        assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize());
        assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId());
        assertEquals(Long.valueOf(100), vmDiskInfoList.get(0).getMinIops());
        assertEquals(Long.valueOf(300), vmDiskInfoList.get(0).getMaxIops());

        assertEquals("disk-offering-uuid-2", vmDiskInfoList.get(1).getDiskOffering().getUuid());
        assertEquals(Long.valueOf(10), vmDiskInfoList.get(1).getSize());
        assertEquals(Long.valueOf(2), vmDiskInfoList.get(1).getDeviceId());
        assertEquals(Long.valueOf(200), vmDiskInfoList.get(1).getMinIops());
        assertEquals(Long.valueOf(400), vmDiskInfoList.get(1).getMaxIops());
    }

    @Test
    public void getDataDiskInfoListFromBackupNullIops() {
        Long size = 5L * 1024 * 1024 * 1024;
        Backup backup = mock(Backup.class);
        Backup.VolumeInfo volumeInfo1 = mock(Backup.VolumeInfo.class);
        when(volumeInfo1.getDiskOfferingId()).thenReturn("disk-offering-uuid-1");
        when(volumeInfo1.getSize()).thenReturn(size);
        when(volumeInfo1.getMinIops()).thenReturn(null);
        when(volumeInfo1.getMaxIops()).thenReturn(null);
        when(volumeInfo1.getType()).thenReturn(Volume.Type.DATADISK);
        when(volumeInfo1.getDeviceId()).thenReturn(1L);
        when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo1));

        DiskOfferingVO diskOffering = mock(DiskOfferingVO.class);
        when(diskOffering.getUuid()).thenReturn("disk-offering-uuid-1");
        when(diskOffering.getState()).thenReturn(DiskOffering.State.Active);

        when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering);

        List<VmDiskInfo> vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup);

        assertEquals(1, vmDiskInfoList.size());
        assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid());
        assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize());
        assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId());
        assertNull(vmDiskInfoList.get(0).getMinIops());
        assertNull(vmDiskInfoList.get(0).getMaxIops());
    }

    @Test (expected = InvalidParameterValueException.class)
    public void testCheckVmDisksSizeAgainstBackup() {
        Long sizeInBackup = 5L * 1024 * 1024 * 1024;
        Long sizeInCmd = 2L;
        Backup backup = mock(Backup.class);
        Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class);
        when(volumeInfo.getDiskOfferingId()).thenReturn("disk-offering-uuid-1");
        when(volumeInfo.getSize()).thenReturn(sizeInBackup);
        when(volumeInfo.getType()).thenReturn(Volume.Type.DATADISK);
        when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo));

        DiskOfferingVO diskOffering = mock(DiskOfferingVO.class);
        when(diskOffering.getState()).thenReturn(DiskOffering.State.Active);
        when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering);
        List<VmDiskInfo> vmDiskInfoList = List.of(new VmDiskInfo(diskOffering, sizeInCmd, 1L, null, null));

        backupManager.checkVmDisksSizeAgainstBackup(vmDiskInfoList, backup);
    }

    @Test
    public void testGetRootDiskInfoFromBackup() {
        Long size = 5L * 1024 * 1024 * 1024;
        Backup backup = mock(Backup.class);
        Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class);
        when(volumeInfo.getDiskOfferingId()).thenReturn("root-disk-offering-uuid");
        when(volumeInfo.getSize()).thenReturn(size);
        when(volumeInfo.getType()).thenReturn(Volume.Type.ROOT);
        when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo));

        DiskOfferingVO diskOffering = mock(DiskOfferingVO.class);
        when(diskOffering.getUuid()).thenReturn("root-disk-offering-uuid");
        when(diskOfferingDao.findByUuid("root-disk-offering-uuid")).thenReturn(diskOffering);

        VmDiskInfo VmDiskInfo = backupManager.getRootDiskInfoFromBackup(backup);

        assertEquals("root-disk-offering-uuid", VmDiskInfo.getDiskOffering().getUuid());
        assertEquals(Long.valueOf(5), VmDiskInfo.getSize());
        assertEquals(null, VmDiskInfo.getDeviceId());
    }

    @Test
    public void testImportBackupOffering() {
        ImportBackupOfferingCmd cmd = Mockito.mock(ImportBackupOfferingCmd.class);
        when(cmd.getZoneId()).thenReturn(1L);
        when(cmd.getExternalId()).thenReturn("external-id");
        when(cmd.getName()).thenReturn("Test Offering");
        when(cmd.getDescription()).thenReturn("Test Description");
        when(cmd.getUserDrivenBackups()).thenReturn(true);

        overrideBackupFrameworkConfigValue();

        when(backupOfferingDao.findByExternalId("external-id", 1L)).thenReturn(null);
        when(backupOfferingDao.findByName("Test Offering", 1L)).thenReturn(null);

        BackupOfferingVO offering = new BackupOfferingVO(1L, "external-id", "testbackupprovider", "Test Offering", "Test Description", true);
        when(backupOfferingDao.persist(any(BackupOfferingVO.class))).thenReturn(offering);
        when(backupProvider.isValidProviderOffering(cmd.getZoneId(), cmd.getExternalId())).thenReturn(true);

        BackupOffering result = backupManager.importBackupOffering(cmd);

        assertEquals("Test Offering", result.getName());
        assertEquals("Test Description", result.getDescription());
        assertEquals(true, result.isUserDrivenBackupAllowed());
        assertEquals("external-id", result.getExternalId());
        assertEquals("testbackupprovider", result.getProvider());
    }

    @Test
    public void testCreateVolumeInfoFromVolumes() {
        Long diskOfferingId = 5L;
        DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class);
        Mockito.when(diskOffering.getUuid()).thenReturn("disk-offering-uuid");
        Mockito.when(diskOfferingDao.findById(diskOfferingId)).thenReturn(diskOffering);

        List<VolumeVO> volumes = new ArrayList<>();
        VolumeVO volume1 = new VolumeVO(Volume.Type.ROOT, "vol1", 1L, 2L, 3L,
                diskOfferingId, null, 1024L, null, null, null);
        volume1.setUuid("uuid1");
        volume1.setPath("path1");
        volume1.setDeviceId(0L);
        volume1.setVolumeType(Volume.Type.ROOT);
        volumes.add(volume1);

        VolumeVO volume2 = new VolumeVO(Volume.Type.ROOT, "vol2", 1L, 2L, 3L,
                diskOfferingId, null, 2048L, 1000L, 2000L, null);
        volume2.setUuid("uuid2");
        volume2.setPath("path2");
        volume2.setDeviceId(1L);
        volume2.setVolumeType(Volume.Type.DATADISK);
        volumes.add(volume2);

        String expectedJson = "[{\"uuid\":\"uuid1\",\"type\":\"ROOT\",\"size\":1024,\"path\":\"path1\",\"deviceId\":0,\"diskOfferingId\":\"disk-offering-uuid\"},{\"uuid\":\"uuid2\",\"type\":\"DATADISK\",\"size\":2048,\"path\":\"path2\",\"deviceId\":1,\"diskOfferingId\":\"disk-offering-uuid\",\"minIops\":1000,\"maxIops\":2000}]";
        String actualJson = backupManager.createVolumeInfoFromVolumes(new ArrayList<>(volumes));

        assertEquals(expectedJson, actualJson);
    }

    @Test
    public void testAssignVMToBackupOffering() {
        Long vmId = 1L;
        Long offeringId = 2L;

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vm.getId()).thenReturn(vmId);
        BackupOfferingVO offering = mock(BackupOfferingVO.class);

        overrideBackupFrameworkConfigValue();

        when(vmInstanceDao.findById(vmId)).thenReturn(vm);
        when(backupOfferingDao.findById(offeringId)).thenReturn(offering);
        when(vm.getState()).thenReturn(VirtualMachine.State.Running);
        when(vm.getDataCenterId()).thenReturn(1L);
        when(vm.getBackupOfferingId()).thenReturn(null);
        when(offering.getProvider()).thenReturn("testbackupprovider");
        when(backupProvider.assignVMToBackupOffering(vm, offering)).thenReturn(true);
        when(vmInstanceDao.update(1L, vm)).thenReturn(true);

        try (MockedStatic<UsageEventUtils> ignored2 = Mockito.mockStatic(UsageEventUtils.class)) {
            boolean result = backupManager.assignVMToBackupOffering(vmId, offeringId);

            assertTrue(result);
            verify(vmInstanceDao, times(1)).findById(vmId);
            verify(backupOfferingDao, times(1)).findById(offeringId);
            verify(backupManager, times(1)).getBackupProvider("testbackupprovider");
        }
    }

    @Test
    public void testRemoveVMFromBackupOffering() {
        Long vmId = 1L;
        Long accountId = 2L;
        Long zoneId = 3L;
        Long offeringId = 4L;
        Long backupScheduleId = 5L;
        String vmHostName = "vm1";
        String vmUuid = "uuid1";
        String resourceName = "Backup-" + vmHostName + "-" + vmUuid;

        boolean forced = true;

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vm.getId()).thenReturn(vmId);
        when(vm.getDataCenterId()).thenReturn(1L);
        when(vm.getBackupOfferingId()).thenReturn(offeringId);
        when(vm.getAccountId()).thenReturn(accountId);
        when(vm.getDataCenterId()).thenReturn(zoneId);
        when(vm.getHostName()).thenReturn(vmHostName);
        when(vm.getUuid()).thenReturn(vmUuid);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        when(vmInstanceDao.update(vmId, vm)).thenReturn(true);

        BackupOfferingVO offering = mock(BackupOfferingVO.class);
        when(backupOfferingDao.findById(vm.getBackupOfferingId())).thenReturn(offering);
        when(offering.getProvider()).thenReturn("testbackupprovider");
        when(backupProvider.removeVMFromBackupOffering(vm)).thenReturn(true);
        when(backupProvider.willDeleteBackupsOnOfferingRemoval()).thenReturn(true);
        when(backupDao.listByVmId(null, vmId)).thenReturn(new ArrayList<>());

        BackupScheduleVO backupSchedule = new BackupScheduleVO();
        ReflectionTestUtils.setField(backupSchedule, "id", backupScheduleId);
        when(backupScheduleDao.listByVM(vmId)).thenReturn(List.of(backupSchedule));

        overrideBackupFrameworkConfigValue();

        try (MockedStatic<UsageEventUtils> usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) {
            boolean result = backupManager.removeVMFromBackupOffering(vmId, forced);

            assertTrue(result);
            verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId);
            verify(backupOfferingDao, times(1)).findById(vm.getBackupOfferingId());
            verify(backupManager, times(1)).getBackupProvider("testbackupprovider");
            verify(backupScheduleDao, times(1)).remove(backupScheduleId);
            usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVED_AND_BACKUPS_DELETED, accountId, zoneId, vmId, resourceName,
                    offeringId, null, null, Backup.class.getSimpleName(), vmUuid));
        }
    }

    @Test
    public void testDeleteBackupScheduleByVmId() {
        Long vmId = 1L;
        Long scheduleId = 2L;
        DeleteBackupScheduleCmd cmd = new DeleteBackupScheduleCmd();
        ReflectionTestUtils.setField(cmd, "vmId", vmId);

        overrideBackupFrameworkConfigValue();

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vmInstanceDao.findById(vmId)).thenReturn(vm);
        BackupScheduleVO schedule = mock(BackupScheduleVO.class);
        when(schedule.getId()).thenReturn(scheduleId);
        when(backupScheduleDao.listByVM(vmId)).thenReturn(List.of(schedule));
        when(backupScheduleDao.remove(scheduleId)).thenReturn(true);

        boolean result = backupManager.deleteBackupSchedule(cmd);
        assertTrue(result);
    }

    @Test
    public void testRestoreBackupToVM() throws NoTransitionException {
        Long backupId = 1L;
        Long vmId = 2L;
        Long hostId = 3L;
        Long offeringId = 4L;
        Long poolId = 5L;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getBackupOfferingId()).thenReturn(offeringId);
        when(backup.getStatus()).thenReturn(Backup.Status.BackedUp);

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        when(vm.getId()).thenReturn(vmId);
        when(vm.getState()).thenReturn(VirtualMachine.State.Stopped);
        when(vm.getHostId()).thenReturn(hostId);

        BackupOfferingVO offering = mock(BackupOfferingVO.class);
        BackupProvider backupProvider = mock(BackupProvider.class);
        when(backupProvider.supportsInstanceFromBackup()).thenReturn(true);

        overrideBackupFrameworkConfigValue();

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering);
        when(offering.getProvider()).thenReturn("testbackupprovider");
        when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider);
        when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId)).thenReturn(true);
        when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringSuccess, hostId)).thenReturn(true);

        VolumeVO rootVolume = mock(VolumeVO.class);
        when(rootVolume.getPoolId()).thenReturn(poolId);
        HostVO host = mock(HostVO.class);
        when(hostDao.findById(hostId)).thenReturn(host);
        StoragePoolVO pool = mock(StoragePoolVO.class);
        when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume));
        when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
        when(rootVolume.getPoolId()).thenReturn(poolId);
        when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume));
        when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
        when(backupProvider.restoreBackupToVM(vm, backup, null, null)).thenReturn(new Pair<>(true, null));

        try (MockedStatic<ActionEventUtils> utils = Mockito.mockStatic(ActionEventUtils.class)) {
            boolean result = backupManager.restoreBackupToVM(backupId, vmId);

            assertTrue(result);
            verify(backupProvider, times(1)).restoreBackupToVM(vm, backup, null, null);
            verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId);
            verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringSuccess, hostId);
        } catch (CloudRuntimeException e) {
            fail("Test failed due to exception" + e);
        }
    }

    @Test
    public void testRestoreBackupToVMException() throws NoTransitionException {
        Long backupId = 1L;
        Long vmId = 2L;
        Long hostId = 3L;
        Long offeringId = 4L;
        Long poolId = 5L;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getBackupOfferingId()).thenReturn(offeringId);
        when(backup.getStatus()).thenReturn(Backup.Status.BackedUp);

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        when(vm.getId()).thenReturn(vmId);
        when(vm.getState()).thenReturn(VirtualMachine.State.Stopped);
        when(vm.getHostId()).thenReturn(hostId);

        BackupOfferingVO offering = mock(BackupOfferingVO.class);
        BackupProvider backupProvider = mock(BackupProvider.class);
        when(backupProvider.supportsInstanceFromBackup()).thenReturn(true);

        overrideBackupFrameworkConfigValue();

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering);
        when(offering.getProvider()).thenReturn("testbackupprovider");
        when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider);
        when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId)).thenReturn(true);
        when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringFailed, hostId)).thenReturn(true);

        VolumeVO rootVolume = mock(VolumeVO.class);
        when(rootVolume.getPoolId()).thenReturn(poolId);
        HostVO host = mock(HostVO.class);
        when(hostDao.findById(hostId)).thenReturn(host);
        StoragePoolVO pool = mock(StoragePoolVO.class);
        when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume));
        when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
        when(rootVolume.getPoolId()).thenReturn(poolId);
        when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume));
        when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
        when(backupProvider.restoreBackupToVM(vm, backup, null, null)).thenReturn(new Pair<>(false, null));

        try (MockedStatic<ActionEventUtils> utils = Mockito.mockStatic(ActionEventUtils.class)) {
            CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
                    () -> backupManager.restoreBackupToVM(backupId, vmId));

            verify(backupProvider, times(1)).restoreBackupToVM(vm, backup, null, null);
            verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId);
            verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringFailed, hostId);
        }
    }

    @Test
    public void testGetBackupStorageUsedStats() {
        Long zoneId = 1L;
        overrideBackupFrameworkConfigValue();
        when(backupManager.getBackupProvider(zoneId)).thenReturn(backupProvider);
        when(backupProvider.getBackupStorageStats(zoneId)).thenReturn(new Pair<>(100L, 200L));

        CapacityVO capacity = backupManager.getBackupStorageUsedStats(zoneId);

        Assert.assertNotNull(capacity);
        Assert.assertEquals(Optional.ofNullable(Long.valueOf(100)), Optional.ofNullable(capacity.getUsedCapacity()));
        Assert.assertEquals(Optional.ofNullable(Long.valueOf(200)), Optional.ofNullable(capacity.getTotalCapacity()));
        Assert.assertEquals(CapacityVO.CAPACITY_TYPE_BACKUP_STORAGE, capacity.getCapacityType());
    }

    @Test
    public void testCheckAndRemoveBackupOfferingBeforeExpunge() {
        Long vmId = 1L;
        Long zoneId = 2L;
        Long offeringId = 3L;
        String vmUuid = "uuid1";
        String instanceName = "i-2-1-VM";
        String backupExternalId = "backup-external-id";

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vm.getId()).thenReturn(vmId);
        when(vm.getUuid()).thenReturn(vmUuid);
        when(vm.getBackupOfferingId()).thenReturn(offeringId);
        when(vm.getInstanceName()).thenReturn(instanceName);
        when(vm.getBackupExternalId()).thenReturn(backupExternalId);
        when(vm.getDataCenterId()).thenReturn(zoneId);
        Backup backup = mock(Backup.class);
        when(backupDao.listByVmIdAndOffering(zoneId, vmId, offeringId)).thenReturn(List.of(backup));

        CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
                () -> backupManager.checkAndRemoveBackupOfferingBeforeExpunge(vm));
        Assert.assertEquals("This Instance [uuid: uuid1, name: i-2-1-VM] has a "
                        + "Backup Offering [id: 3, external id: backup-external-id] with 1 backups. Please, remove the backup offering "
                        + "before proceeding to VM exclusion!", exception.getMessage());
    }

    @Test
    public void testGetIpToNetworkMapFromBackup() {
        Long networkId1 = 1L;
        Long networkId2 = 2L;
        String networkUuid1 = "network-uuid-1";
        String networkUuid2 = "network-uuid-2";
        String ip1 = "10.1.1.1";
        String ip2 = "10.1.1.2";
        String ipv61 = "2001:db8::1";
        String ipv62 = "2001:db8::2";
        String mac1 = "00:11:22:33:44:55";
        String mac2 = "00:11:22:33:44:56";

        // Test case 1: Missing network information
        Backup backup1 = mock(Backup.class);
        List<Long> networkIds1 = new ArrayList<>();
        try {
            backupManager.getIpToNetworkMapFromBackup(backup1, true, networkIds1);
            fail("Expected CloudRuntimeException for missing network information");
        } catch (CloudRuntimeException e) {
            assertEquals("Backup doesn't contain network information. Please specify at least one valid network while creating instance", e.getMessage());
        }

        // Test case 2: IP preservation enabled with IP information
        Backup backup2 = mock(Backup.class);
        String nicsJson = String.format("[{\"networkid\":\"%s\",\"ipaddress\":\"%s\",\"ip6address\":\"%s\",\"macaddress\":\"%s\"}," +
                        "{\"networkid\":\"%s\",\"ipaddress\":\"%s\",\"ip6address\":\"%s\",\"macaddress\":\"%s\"}]",
                        networkUuid1, ip1, ipv61, mac1, networkUuid2, ip2, ipv62, mac2);
        when(backup2.getDetail(ApiConstants.NICS)).thenReturn(nicsJson);

        NetworkVO network1 = mock(NetworkVO.class);
        NetworkVO network2 = mock(NetworkVO.class);
        when(networkDao.findByUuid(networkUuid1)).thenReturn(network1);
        when(networkDao.findByUuid(networkUuid2)).thenReturn(network2);
        when(network1.getId()).thenReturn(networkId1);
        when(network2.getId()).thenReturn(networkId2);

        Network.IpAddresses ipAddresses1 = mock(Network.IpAddresses.class);
        Network.IpAddresses ipAddresses2 = mock(Network.IpAddresses.class);
        when(networkService.getIpAddressesFromIps(ip1, ipv61, mac1)).thenReturn(ipAddresses1);
        when(networkService.getIpAddressesFromIps(ip2, ipv62, mac2)).thenReturn(ipAddresses2);

        List<Long> networkIds2 = new ArrayList<>();
        Map<Long, Network.IpAddresses> result2 = backupManager.getIpToNetworkMapFromBackup(backup2, true, networkIds2);

        assertEquals(2, result2.size());
        assertEquals(ipAddresses1, result2.get(networkId1));
        assertEquals(ipAddresses2, result2.get(networkId2));
        assertEquals(2, networkIds2.size());
        assertTrue(networkIds2.contains(networkId1));
        assertTrue(networkIds2.contains(networkId2));

        // Test case 3: IP preservation enabled but missing IP information
        Backup backup3 = mock(Backup.class);
        nicsJson = String.format("[{\"networkid\":\"%s\"}]", networkUuid1);
        when(backup3.getDetail(ApiConstants.NICS)).thenReturn(nicsJson);

        List<Long> networkIds3 = new ArrayList<>();
        Map<Long, Network.IpAddresses> result3 = backupManager.getIpToNetworkMapFromBackup(backup3, true, networkIds3);

        assertEquals(1, result3.size());
        assertNull(result3.get(networkId1));
        assertEquals(1, networkIds3.size());
        assertTrue(networkIds3.contains(networkId1));

        // Test case 4: IP preservation disabled
        Backup backup4 = mock(Backup.class);
        nicsJson = String.format("[{\"networkid\":\"%s\"}]", networkUuid1);
        when(backup4.getDetail(ApiConstants.NICS)).thenReturn(nicsJson);

        List<Long> networkIds4 = new ArrayList<>();
        Map<Long, Network.IpAddresses> result4 = backupManager.getIpToNetworkMapFromBackup(backup4, false, networkIds4);

        assertEquals(1, result4.size());
        assertNull(result4.get(networkId1));
        assertEquals(1, networkIds4.size());
        assertTrue(networkIds4.contains(networkId1));
    }

    @Test
    public void testDeleteBackupVmNotFound() throws ResourceAllocationException {
        Long backupId = 1L;
        Long vmId = 2L;
        Long zoneId = 3L;
        Long accountId = 4L;
        Long backupOfferingId = 5L;
        String vmHostName = "vm1";
        String vmUuid = "uuid1";
        String resourceName = "Backup-" + vmHostName + "-" + vmUuid;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getId()).thenReturn(backupId);
        when(backup.getVmId()).thenReturn(vmId);
        when(backup.getZoneId()).thenReturn(zoneId);
        when(backup.getAccountId()).thenReturn(accountId);
        when(accountManager.getAccount(accountId)).thenReturn(accountVOMock);
        when(backup.getBackupOfferingId()).thenReturn(backupOfferingId);
        when(backup.getSize()).thenReturn(100L);

        overrideBackupFrameworkConfigValue();

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vm.getId()).thenReturn(vmId);
        when(vm.getAccountId()).thenReturn(accountId);
        when(vm.getBackupOfferingId()).thenReturn(10L);
        when(vm.getDataCenterId()).thenReturn(zoneId);
        when(vm.getHostName()).thenReturn(vmHostName);
        when(vm.getUuid()).thenReturn(vmUuid);
        when(backupDao.findByIdIncludingRemoved(backupId)).thenReturn(backup);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        when(backupDao.listByVmIdAndOffering(zoneId, vmId, backupOfferingId)).thenReturn(new ArrayList<>());

        BackupOfferingVO offering = mock(BackupOfferingVO.class);
        when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering);

        when(backupProvider.deleteBackup(backup, false)).thenReturn(true);

        when(backupDao.remove(backupId)).thenReturn(true);

        try (MockedStatic<UsageEventUtils> usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) {
            boolean result = backupManager.deleteBackup(backupId, false);

            assertTrue(result);
            verify(backupProvider).deleteBackup(backup, false);
            verify(resourceLimitMgr).decrementResourceCount(accountId, Resource.ResourceType.backup);
            verify(resourceLimitMgr).decrementResourceCount(accountId, Resource.ResourceType.backup_storage, backup.getSize());
            verify(backupDao).remove(backupId);
            usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVED_AND_BACKUPS_DELETED, accountId, zoneId, vmId, resourceName,
                    backupOfferingId, null, null, Backup.class.getSimpleName(), vmUuid));
        }
    }

    @Test
    public void testNewBackupResponse() {
        Long vmId = 1L;
        Long accountId = 2L;
        Long domainId = 3L;
        Long zoneId = 4L;
        Long vmOfferingId = 5L;
        Long backupOfferingId = 6L;
        Long backupId = 7L;
        Long templateId = 8L;
        String templateUuid = "template-uuid1";
        String serviceOfferingUuid = "service-offering-uuid1";

        BackupVO backup = new BackupVO();
        ReflectionTestUtils.setField(backup, "id", backupId);
        ReflectionTestUtils.setField(backup, "uuid", "backup-uuid");
        backup.setVmId(vmId);
        backup.setAccountId(accountId);
        backup.setDomainId(domainId);
        backup.setZoneId(zoneId);
        backup.setBackupOfferingId(backupOfferingId);
        backup.setType("Full");
        backup.setBackupScheduleId(null);

        VMInstanceVO vm = new VMInstanceVO(vmId, 0L, "test-vm", "test-vm", VirtualMachine.Type.User,
                0L, Hypervisor.HypervisorType.Simulator, 0L, domainId, accountId, 0L, false);
        vm.setDataCenterId(zoneId);
        vm.setBackupOfferingId(vmOfferingId);
        vm.setTemplateId(templateId);

        AccountVO account = new AccountVO();
        account.setUuid("account-uuid");
        account.setAccountName("test-account");

        DomainVO domain = new DomainVO();
        domain.setUuid("domain-uuid");
        domain.setName("test-domain");

        DataCenterVO zone = new DataCenterVO(1L, "test-zone", null, null, null, null, null, null, null, null, DataCenter.NetworkType.Advanced, null, null);
        zone.setUuid("zone-uuid");

        BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class);
        Mockito.when(offering.getUuid()).thenReturn("offering-uuid");
        Mockito.when(offering.getName()).thenReturn("test-offering");

        Mockito.when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        Mockito.when(accountDao.findByIdIncludingRemoved(accountId)).thenReturn(account);
        Mockito.when(domainDao.findByIdIncludingRemoved(domainId)).thenReturn(domain);
        Mockito.when(dataCenterDao.findByIdIncludingRemoved(zoneId)).thenReturn(zone);
        Mockito.when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering);

        VMTemplateVO template = mock(VMTemplateVO.class);
        when(template.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
        when(template.getName()).thenReturn("template1");
        when(vmTemplateDao.findByUuid(templateUuid)).thenReturn(template);
        Map<String, String> details = new HashMap<>();
        details.put(ApiConstants.TEMPLATE_ID, templateUuid);

        ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class);
        when(serviceOffering.getName()).thenReturn("service-offering1");
        when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering);
        details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid);

        NetworkVO network = mock(NetworkVO.class);
        when(network.getName()).thenReturn("network1");
        when(networkDao.findByUuid("network-uuid1")).thenReturn(network);
        details.put(ApiConstants.NICS, "[{\"networkid\":\"network-uuid1\"}]");

        Mockito.when(backupDetailsDao.listDetailsKeyPairs(backup.getId(), true)).thenReturn(details);

        BackupResponse response = backupManager.createBackupResponse(backup, true);

        Assert.assertEquals("backup-uuid", response.getId());
        Assert.assertEquals("test-vm", response.getVmName());
        Assert.assertEquals("account-uuid", response.getAccountId());
        Assert.assertEquals("test-account", response.getAccount());
        Assert.assertEquals("domain-uuid", response.getDomainId());
        Assert.assertEquals("test-domain", response.getDomain());
        Assert.assertEquals("zone-uuid", response.getZoneId());
        Assert.assertEquals("test-zone", response.getZone());
        Assert.assertEquals("offering-uuid", response.getBackupOfferingId());
        Assert.assertEquals("test-offering", response.getBackupOffering());
        Assert.assertEquals("MANUAL", response.getIntervalType());
        Assert.assertEquals("{serviceofferingid=service-offering-uuid1, isiso=false, hypervisor=Simulator, " +
                "nics=[{\"networkid\":\"network-uuid1\",\"networkname\":\"network1\"}], serviceofferingname=service-offering1, " +
                "templatename=template1, templateid=template-uuid1}", response.getVmDetails().toString());
        Assert.assertEquals(true, response.getVmOfferingRemoved());
    }

    @Test
    public void validateAndGetDefaultBackupRetentionIfRequiredTestReturnZeroAsDefaultValue() {
        int retention = backupManager.validateAndGetDefaultBackupRetentionIfRequired(null, backupOfferingVOMock, null);
        assertEquals(0, retention);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void validateAndGetDefaultBackupRetentionIfRequiredTestThrowExceptionWhenBackupOfferingProviderIsVeeam() {
        Mockito.when(backupOfferingVOMock.getProvider()).thenReturn("veeam");
        backupManager.validateAndGetDefaultBackupRetentionIfRequired(1, backupOfferingVOMock, vmInstanceVOMock);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void validateAndGetDefaultBackupRetentionIfRequiredTestThrowExceptionWhenMaxBackupsIsLessThanZero() {
        backupManager.validateAndGetDefaultBackupRetentionIfRequired(-1, backupOfferingVOMock, vmInstanceVOMock);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void validateAndGetDefaultBackupRetentionIfRequiredTestThrowExceptionWhenMaxBackupsExceedsAccountLimit() {
        int maxBackups = 6;
        long accountId = 1L;
        long accountLimit = 5L;
        long domainId = 10L;
        long domainLimit = -1L;

        when(vmInstanceVOMock.getAccountId()).thenReturn(accountId);
        when(accountManager.getAccount(accountId)).thenReturn(accountVOMock);
        when(resourceLimitMgr.findCorrectResourceLimitForAccount(accountVOMock, Resource.ResourceType.backup, null)).thenReturn(accountLimit);
        when(accountVOMock.getDomainId()).thenReturn(domainId);
        when(domainManager.getDomain(domainId)).thenReturn(domainMock);
        when(resourceLimitMgr.findCorrectResourceLimitForDomain(domainMock, Resource.ResourceType.backup, null)).thenReturn(domainLimit);
        when(accountVOMock.getId()).thenReturn(accountId);
        when(accountManager.isRootAdmin(accountId)).thenReturn(false);

        backupManager.validateAndGetDefaultBackupRetentionIfRequired(maxBackups, backupOfferingVOMock, vmInstanceVOMock);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void validateAndGetDefaultBackupRetentionIfRequiredTestThrowExceptionWhenMaxBackupsExceedsDomainLimit() {
        int maxBackups = 6;
        long accountId = 1L;
        long accountLimit = -1L;
        long domainId = 10L;
        long domainLimit = 5L;

        when(vmInstanceVOMock.getAccountId()).thenReturn(accountId);
        when(accountManager.getAccount(accountId)).thenReturn(accountVOMock);
        when(resourceLimitMgr.findCorrectResourceLimitForAccount(accountVOMock, Resource.ResourceType.backup, null)).thenReturn(accountLimit);
        when(accountVOMock.getDomainId()).thenReturn(domainId);
        when(domainManager.getDomain(domainId)).thenReturn(domainMock);
        when(resourceLimitMgr.findCorrectResourceLimitForDomain(domainMock, Resource.ResourceType.backup, null)).thenReturn(domainLimit);
        when(accountVOMock.getId()).thenReturn(accountId);
        when(accountManager.isRootAdmin(accountId)).thenReturn(false);

        backupManager.validateAndGetDefaultBackupRetentionIfRequired(maxBackups, backupOfferingVOMock, vmInstanceVOMock);
    }

    @Test
    public void validateAndGetDefaultBackupRetentionIfRequiredTestIgnoreLimitCheckWhenAccountIsRootAdmin() {
        int maxBackups = 6;
        long accountId = 1L;
        long accountLimit = 5L;
        long domainId = 10L;
        long domainLimit = 5L;

        when(vmInstanceVOMock.getAccountId()).thenReturn(accountId);
        when(accountManager.getAccount(accountId)).thenReturn(accountVOMock);
        when(resourceLimitMgr.findCorrectResourceLimitForAccount(accountVOMock, Resource.ResourceType.backup, null)).thenReturn(accountLimit);
        when(accountVOMock.getDomainId()).thenReturn(domainId);
        when(domainManager.getDomain(domainId)).thenReturn(domainMock);
        when(resourceLimitMgr.findCorrectResourceLimitForDomain(domainMock, Resource.ResourceType.backup, null)).thenReturn(domainLimit);
        when(accountVOMock.getId()).thenReturn(accountId);
        when(accountManager.isRootAdmin(accountId)).thenReturn(true);

        int retention = backupManager.validateAndGetDefaultBackupRetentionIfRequired(maxBackups, backupOfferingVOMock, vmInstanceVOMock);
        assertEquals(maxBackups, retention);
    }

    @Test
    public void getBackupScheduleTestReturnNullWhenBackupIsManual() {
        String jobParams = "{}";
        when(asyncJobVOMock.getCmdInfo()).thenReturn(jobParams);
        when(asyncJobVOMock.getId()).thenReturn(1L);

        Long backupScheduleId = backupManager.getBackupScheduleId(asyncJobVOMock);
        assertNull(backupScheduleId);
    }

    @Test
    public void getBackupScheduleTestReturnBackupScheduleIdWhenBackupIsScheduled() {
        Map<String, String> params = Map.of(
                ApiConstants.SCHEDULE_ID, "100"
        );
        String jobParams = gson.toJson(params);
        when(asyncJobVOMock.getCmdInfo()).thenReturn(jobParams);
        when(asyncJobVOMock.getId()).thenReturn(1L);

        Long backupScheduleId = backupManager.getBackupScheduleId(asyncJobVOMock);
        assertEquals(Long.valueOf("100"), backupScheduleId);
    }

    @Test
    public void getBackupScheduleTestReturnNullWhenSpecifiedBackupScheduleIdIsNotALongValue() {
        Map<String, String> params = Map.of(
                ApiConstants.SCHEDULE_ID, "InvalidValue"
        );
        String jobParams = gson.toJson(params);
        when(asyncJobVOMock.getCmdInfo()).thenReturn(jobParams);
        when(asyncJobVOMock.getId()).thenReturn(1L);

        Long backupScheduleId = backupManager.getBackupScheduleId(asyncJobVOMock);
        assertNull(backupScheduleId);
    }

    @Test
    public void deleteOldestBackupFromScheduleIfRequiredTestSkipDeletionWhenBackupScheduleIsNotFound() throws ResourceAllocationException {
        backupManager.deleteOldestBackupFromScheduleIfRequired(1L, 1L);
        Mockito.verify(backupManager, Mockito.never()).deleteExcessBackups(Mockito.anyList(), Mockito.anyInt(), Mockito.anyLong());
    }

    @Test
    public void deleteOldestBackupFromScheduleIfRequiredTestSkipDeletionWhenRetentionIsEqualToZero() throws ResourceAllocationException {
        Mockito.when(backupScheduleDao.findById(1L)).thenReturn(backupScheduleVOMock);
        Mockito.when(backupScheduleVOMock.getMaxBackups()).thenReturn(0);
        backupManager.deleteOldestBackupFromScheduleIfRequired(1L, 1L);
        Mockito.verify(backupManager, Mockito.never()).deleteExcessBackups(Mockito.anyList(), Mockito.anyInt(), Mockito.anyLong());
    }

    @Test
    public void deleteOldestBackupFromScheduleIfRequiredTestSkipDeletionWhenAmountOfBackupsToBeDeletedIsLessThanOne() throws ResourceAllocationException {
        List<BackupVO> backups = List.of(Mockito.mock(BackupVO.class), Mockito.mock(BackupVO.class));
        Mockito.when(backupScheduleDao.findById(1L)).thenReturn(backupScheduleVOMock);
        Mockito.when(backupScheduleVOMock.getMaxBackups()).thenReturn(2);
        Mockito.when(backupDao.listBySchedule(1L)).thenReturn(backups);
        backupManager.deleteOldestBackupFromScheduleIfRequired(1L, 1L);
        Mockito.verify(backupManager, Mockito.never()).deleteExcessBackups(Mockito.anyList(), Mockito.anyInt(), Mockito.anyLong());
    }

    @Test
    public void deleteOldestBackupFromScheduleIfRequiredTestDeleteBackupsWhenRequired() throws ResourceAllocationException {
        List<BackupVO> backups = List.of(Mockito.mock(BackupVO.class), Mockito.mock(BackupVO.class));
        Mockito.when(backupScheduleDao.findById(1L)).thenReturn(backupScheduleVOMock);
        Mockito.when(backupScheduleVOMock.getMaxBackups()).thenReturn(1);
        Mockito.when(backupDao.listBySchedule(1L)).thenReturn(backups);
        Mockito.doNothing().when(backupManager).deleteExcessBackups(Mockito.anyList(), Mockito.anyInt(), Mockito.anyLong());
        backupManager.deleteOldestBackupFromScheduleIfRequired(1L, 1L);
        Mockito.verify(backupManager).deleteExcessBackups(Mockito.anyList(), Mockito.anyInt(), Mockito.anyLong());
    }

    @Test
    public void deleteExcessBackupsTestEnsureBackupsAreDeletedWhenMethodIsCalled() throws ResourceAllocationException {
        try (MockedStatic<ActionEventUtils> actionEventUtils = Mockito.mockStatic(ActionEventUtils.class)) {
            List<BackupVO> backups = List.of(Mockito.mock(BackupVO.class),
                    Mockito.mock(BackupVO.class),
                    Mockito.mock(BackupVO.class));

            Mockito.when(backups.get(0).getId()).thenReturn(1L);
            Mockito.when(backups.get(1).getId()).thenReturn(2L);
            Mockito.when(backups.get(0).getAccountId()).thenReturn(1L);
            Mockito.when(backups.get(1).getAccountId()).thenReturn(2L);
            Mockito.doReturn(true).when(backupManager).deleteBackup(Mockito.anyLong(), Mockito.eq(false));

            actionEventUtils.when(() -> ActionEventUtils.onStartedActionEvent(
                    Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
                    Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
                    Mockito.anyBoolean(), Mockito.anyInt())).thenReturn(1L);
            actionEventUtils.when(() -> ActionEventUtils.onCompletedActionEvent(
                    Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyInt())).thenReturn(2L);

            backupManager.deleteExcessBackups(backups, 2, 1L);
            Mockito.verify(backupManager, times(2)).deleteBackup(Mockito.anyLong(), Mockito.eq(false));
        }
    }

    @Test
    public void sendExceededBackupLimitAlertTestSendAlertForBackupResourceType() {
        String accountUuid = UUID.randomUUID().toString();
        String expectedMessage = "Failed to create backup: backup resource limit exceeded for account with ID: " + accountUuid + ".";
        String expectedAlertDetails = expectedMessage + " Please, use the 'updateResourceLimit' API to increase the backup limit.";

        backupManager.sendExceededBackupLimitAlert(accountUuid, Resource.ResourceType.backup);
        verify(alertManagerMock).sendAlert(
                AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT,
                0L,
                0L,
                expectedMessage,
                expectedAlertDetails
        );
    }

    @Test
    public void sendExceededBackupLimitAlertTestSendAlertForBackupStorageResourceType() {
        String accountUuid = UUID.randomUUID().toString();
        String expectedMessage = "Failed to create backup: backup storage space resource limit exceeded for account with ID: " + accountUuid + ".";
        String expectedAlertDetails = expectedMessage + " Please, use the 'updateResourceLimit' API to increase the backup limit.";

        backupManager.sendExceededBackupLimitAlert(accountUuid, Resource.ResourceType.backup_storage);
        verify(alertManagerMock).sendAlert(
                AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT,
                0L,
                0L,
                expectedMessage,
                expectedAlertDetails
        );
    }

    @Test
    public void testListBackupSchedulesAsRootAdmin() {
        long vmId = 1L;
        ListBackupScheduleCmd cmd = Mockito.mock(ListBackupScheduleCmd.class);
        Mockito.when(cmd.getVmId()).thenReturn(vmId);
        Mockito.when(cmd.getId()).thenReturn(1L);

        // Mock VM for validation
        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
        Mockito.when(vmInstanceDao.findById(vmId)).thenReturn(vm);
        Mockito.when(vm.getDataCenterId()).thenReturn(1L);
        overrideBackupFrameworkConfigValue();
        Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any());

        BackupScheduleVO schedule1 = Mockito.mock(BackupScheduleVO.class);
        BackupScheduleVO schedule2 = Mockito.mock(BackupScheduleVO.class);
        List<BackupScheduleVO> schedules = List.of(schedule1, schedule2);

        SearchBuilder<BackupScheduleVO> searchBuilder = Mockito.mock(SearchBuilder.class);
        SearchCriteria<BackupScheduleVO> searchCriteria = Mockito.mock(SearchCriteria.class);
        BackupScheduleVO entity = Mockito.mock(BackupScheduleVO.class);

        Mockito.when(backupScheduleDao.createSearchBuilder()).thenReturn(searchBuilder);
        Mockito.when(searchBuilder.entity()).thenReturn(entity);
        Mockito.when(searchBuilder.and(Mockito.anyString(), (Object) any(), Mockito.any())).thenReturn(searchBuilder);
        Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);

        Mockito.when(backupScheduleDao.searchAndCount(Mockito.any(), Mockito.any())).thenReturn(new Pair<>(schedules, schedules.size()));
        List<BackupSchedule> result = backupManager.listBackupSchedules(cmd);

        assertEquals(2, result.size());
        assertTrue(result.contains(schedule1));
        assertTrue(result.contains(schedule2));
    }

    @Test
    public void testListBackupSchedulesAsNonAdmin() {
        long vmId = 1L;
        ListBackupScheduleCmd cmd = Mockito.mock(ListBackupScheduleCmd.class);
        Mockito.when(cmd.getVmId()).thenReturn(vmId);
        Mockito.when(cmd.getId()).thenReturn(1L);

        // Mock VM for validation
        VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
        Mockito.when(vmInstanceDao.findById(vmId)).thenReturn(vm);
        Mockito.when(vm.getDataCenterId()).thenReturn(1L);
        overrideBackupFrameworkConfigValue();
        Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any());

        BackupScheduleVO schedule = Mockito.mock(BackupScheduleVO.class);
        List<BackupScheduleVO> schedules = List.of(schedule);

        SearchBuilder<BackupScheduleVO> searchBuilder = Mockito.mock(SearchBuilder.class);
        SearchCriteria<BackupScheduleVO> searchCriteria = Mockito.mock(SearchCriteria.class);
        BackupScheduleVO entity = Mockito.mock(BackupScheduleVO.class);

        Mockito.when(backupScheduleDao.createSearchBuilder()).thenReturn(searchBuilder);
        Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
        Mockito.when(searchBuilder.entity()).thenReturn(entity);
        Mockito.when(searchBuilder.and(Mockito.anyString(), (Object) any(), Mockito.any())).thenReturn(searchBuilder);
        Mockito.lenient().when(backupScheduleDao.search(Mockito.eq(searchCriteria), Mockito.any())).thenReturn(schedules);

        Mockito.doNothing().when(accountManager).buildACLSearchBuilder(Mockito.any(), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any());
        Mockito.doNothing().when(accountManager).buildACLSearchCriteria(Mockito.any(), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any());

        Mockito.when(backupScheduleDao.searchAndCount(Mockito.any(), Mockito.any())).thenReturn(new Pair<>(schedules, schedules.size()));
        List<BackupSchedule> result = backupManager.listBackupSchedules(cmd);

        assertEquals(1, result.size());
        assertTrue(result.contains(schedule));
    }

    @Test
    public void testCanCreateInstanceFromBackupAcrossZonesSuccess() {
        Long backupId = 1L;
        Long backupOfferingId = 2L;
        String providerName = "testbackupprovider";

        BackupVO backup = mock(BackupVO.class);
        when(backup.getBackupOfferingId()).thenReturn(backupOfferingId);

        BackupOfferingVO offering = mock(BackupOfferingVO.class);
        when(offering.getProvider()).thenReturn(providerName);

        BackupProvider backupProvider = mock(BackupProvider.class);
        when(backupProvider.crossZoneInstanceCreationEnabled(offering)).thenReturn(true);

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering);
        when(backupManager.getBackupProvider(providerName)).thenReturn(backupProvider);

        Boolean result = backupManager.canCreateInstanceFromBackupAcrossZones(backupId);

        assertTrue(result);
        verify(backupDao, times(1)).findById(backupId);
        verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId);
        verify(backupManager, times(1)).getBackupProvider(providerName);
        verify(backupProvider, times(1)).crossZoneInstanceCreationEnabled(offering);
    }

    @Test
    public void testCanCreateInstanceFromBackupAcrossZonesFalse() {
        Long backupId = 1L;
        Long backupOfferingId = 2L;
        String providerName = "testbackupprovider";

        BackupVO backup = mock(BackupVO.class);
        when(backup.getBackupOfferingId()).thenReturn(backupOfferingId);

        BackupOfferingVO offering = mock(BackupOfferingVO.class);
        when(offering.getProvider()).thenReturn(providerName);

        BackupProvider backupProvider = mock(BackupProvider.class);
        when(backupProvider.crossZoneInstanceCreationEnabled(offering)).thenReturn(false);

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering);
        when(backupManager.getBackupProvider(providerName)).thenReturn(backupProvider);

        Boolean result = backupManager.canCreateInstanceFromBackupAcrossZones(backupId);

        assertFalse(result);
        verify(backupDao, times(1)).findById(backupId);
        verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId);
        verify(backupManager, times(1)).getBackupProvider(providerName);
        verify(backupProvider, times(1)).crossZoneInstanceCreationEnabled(offering);
    }

    @Test
    public void testCanCreateInstanceFromBackupAcrossZonesOfferingNotFound() {
        Long backupId = 1L;
        Long backupOfferingId = 2L;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getBackupOfferingId()).thenReturn(backupOfferingId);

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(null);

        CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
                () -> backupManager.canCreateInstanceFromBackupAcrossZones(backupId));

        assertEquals("Failed to find backup offering", exception.getMessage());
        verify(backupDao, times(1)).findById(backupId);
        verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId);
        verify(backupManager, never()).getBackupProvider(any(String.class));
    }

    @Test
    public void testRestoreBackupSuccess() throws NoTransitionException {
        Long backupId = 1L;
        Long vmId = 2L;
        Long zoneId = 3L;
        Long accountId = 4L;
        Long domainId = 5L;
        Long userId = 6L;
        Long offeringId = 7L;
        String vmInstanceName = "test-vm";
        Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getVmId()).thenReturn(vmId);
        when(backup.getZoneId()).thenReturn(zoneId);
        when(backup.getStatus()).thenReturn(Backup.Status.BackedUp);
        when(backup.getBackupOfferingId()).thenReturn(offeringId);
        Backup.VolumeInfo volumeInfo = new Backup.VolumeInfo("uuid", "path", Volume.Type.ROOT, 1024L, 0L, "disk-offering-uuid", 1000L, 2000L);
        when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo));
        when(backup.getUuid()).thenReturn("backup-uuid");

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vm.getId()).thenReturn(vmId);
        when(vm.getDataCenterId()).thenReturn(zoneId);
        when(vm.getDomainId()).thenReturn(domainId);
        when(vm.getAccountId()).thenReturn(accountId);
        when(vm.getUserId()).thenReturn(userId);
        when(vm.getInstanceName()).thenReturn(vmInstanceName);
        when(vm.getHypervisorType()).thenReturn(hypervisorType);
        when(vm.getState()).thenReturn(VirtualMachine.State.Stopped);
        when(vm.getRemoved()).thenReturn(null);
        when(vm.getBackupOfferingId()).thenReturn(offeringId);

        BackupOfferingVO offering = mock(BackupOfferingVO.class);
        when(offering.getProvider()).thenReturn("testbackupprovider");

        VolumeVO volume = mock(VolumeVO.class);
        when(volumeDao.findByInstance(vmId)).thenReturn(Collections.singletonList(volume));

        BackupProvider backupProvider = mock(BackupProvider.class);
        when(backupProvider.restoreVMFromBackup(vm, backup)).thenReturn(true);

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering);
        when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider);
        doReturn(true).when(backupManager).importRestoredVM(zoneId, domainId, accountId, userId, vmInstanceName, hypervisorType, backup);
        doNothing().when(backupManager).validateBackupForZone(any());
        when(virtualMachineManager.stateTransitTo(any(), any(), any())).thenReturn(true);

        try (MockedStatic<ActionEventUtils> utils = Mockito.mockStatic(ActionEventUtils.class)) {
            Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
                    Mockito.eq(true), Mockito.eq(0))).thenReturn(1L);

            boolean result = backupManager.restoreBackup(backupId);

            assertTrue(result);
            verify(backupDao, times(1)).findById(backupId);
            verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId);
            verify(backupOfferingDao, times(2)).findByIdIncludingRemoved(offeringId);
            verify(backupProvider, times(1)).restoreVMFromBackup(vm, backup);
            verify(backupManager, times(1)).importRestoredVM(zoneId, domainId, accountId, userId, vmInstanceName, hypervisorType, backup);
        }
    }

    @Test
    public void testRestoreBackupBackupNotFound() {
        Long backupId = 1L;

        when(backupDao.findById(backupId)).thenReturn(null);

        CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
                () -> backupManager.restoreBackup(backupId));

        assertEquals("Backup " + backupId + " does not exist", exception.getMessage());
        verify(backupDao, times(1)).findById(backupId);
        verify(vmInstanceDao, never()).findByIdIncludingRemoved(any());
    }

    @Test
    public void testRestoreBackupBackupNotBackedUp() {
        Long backupId = 1L;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getStatus()).thenReturn(Backup.Status.BackingUp);

        when(backupDao.findById(backupId)).thenReturn(backup);

        CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
                () -> backupManager.restoreBackup(backupId));

        assertEquals("Backup should be in BackedUp state", exception.getMessage());
        verify(backupDao, times(1)).findById(backupId);
        verify(vmInstanceDao, never()).findByIdIncludingRemoved(any());
    }

    @Test
    public void testRestoreBackupVmExpunging() {
        Long backupId = 1L;
        Long vmId = 2L;
        Long zoneId = 3L;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getVmId()).thenReturn(vmId);
        when(backup.getZoneId()).thenReturn(zoneId);
        when(backup.getStatus()).thenReturn(Backup.Status.BackedUp);

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vm.getState()).thenReturn(VirtualMachine.State.Expunging);

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        doNothing().when(backupManager).validateBackupForZone(any());

        CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
                () -> backupManager.restoreBackup(backupId));

        assertEquals("The Instance from which the backup was taken could not be found.", exception.getMessage());
        verify(backupDao, times(1)).findById(backupId);
        verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId);
    }

    @Test
    public void testRestoreBackupVmNotStopped() {
        Long backupId = 1L;
        Long vmId = 2L;
        Long zoneId = 3L;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getVmId()).thenReturn(vmId);
        when(backup.getZoneId()).thenReturn(zoneId);
        when(backup.getStatus()).thenReturn(Backup.Status.BackedUp);

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vm.getState()).thenReturn(VirtualMachine.State.Running);
        when(vm.getRemoved()).thenReturn(null);

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        doNothing().when(backupManager).validateBackupForZone(any());

        CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
                () -> backupManager.restoreBackup(backupId));

        assertEquals("Existing VM should be stopped before being restored from backup", exception.getMessage());
        verify(backupDao, times(1)).findById(backupId);
        verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId);
    }

    @Test
    public void testRestoreBackupVolumeMismatch() {
        Long backupId = 1L;
        Long vmId = 2L;
        Long zoneId = 3L;

        BackupVO backup = mock(BackupVO.class);
        when(backup.getVmId()).thenReturn(vmId);
        when(backup.getZoneId()).thenReturn(zoneId);
        when(backup.getStatus()).thenReturn(Backup.Status.BackedUp);
        when(backup.getBackedUpVolumes()).thenReturn(Collections.emptyList());

        VMInstanceVO vm = mock(VMInstanceVO.class);
        when(vm.getId()).thenReturn(vmId);
        when(vm.getState()).thenReturn(VirtualMachine.State.Destroyed);
        when(vm.getRemoved()).thenReturn(null);
        when(vm.getBackupVolumeList()).thenReturn(Collections.emptyList());

        VolumeVO volume = mock(VolumeVO.class);
        when(volumeDao.findByInstance(vmId)).thenReturn(Collections.singletonList(volume));

        when(backupDao.findById(backupId)).thenReturn(backup);
        when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm);
        doNothing().when(backupManager).validateBackupForZone(any());

        try (MockedStatic<ActionEventUtils> utils = Mockito.mockStatic(ActionEventUtils.class)) {
            Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(),
                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
                    Mockito.eq(true), Mockito.eq(0))).thenReturn(1L);
            CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class,
                    () -> backupManager.restoreBackup(backupId));

            assertEquals("Unable to restore VM with the current backup as the backup has different number of disks as the VM", exception.getMessage());
        }
        verify(backupDao, times(1)).findById(backupId);
        verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId);
        verify(volumeDao, times(1)).findByInstance(vmId);
    }
}
