org.codice.ddf.configuration.migration.ConfigurationMigrationManagerTest.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.configuration.migration.ConfigurationMigrationManagerTest.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.configuration.migration;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.management.MBeanException;
import javax.management.ObjectName;
import org.apache.commons.io.FileUtils;
import org.apache.karaf.system.SystemService;
import org.codice.ddf.migration.Migratable;
import org.codice.ddf.migration.MigrationException;
import org.codice.ddf.migration.MigrationInformation;
import org.codice.ddf.migration.MigrationMessage;
import org.codice.ddf.migration.MigrationReport;
import org.codice.ddf.migration.MigrationWarning;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.StringDescription;
import org.hamcrest.io.FileMatchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class ConfigurationMigrationManagerTest extends AbstractMigrationSupport {

    public static final String TEST_BRANDING = "test";

    public static final String TEST_VERSION = "1.0";

    public static final String TEST_MESSAGE = "Test message.";

    public static final String TEST_DIRECTORY = "exported";

    private static final String WRAPPER_KEY = "wrapper.key";

    private static final String RESTART_JVM = "karaf.restart.jvm";

    private ConfigurationMigrationManager configurationMigrationManager;

    private List<Migratable> migratables;

    @Mock
    private SystemService mockSystemService;

    @Mock
    private WrapperManagerMXBean mockWrapperManager;

    private Path path;

    private String encryptedFileName;
    private String decryptedFileName;

    @Before
    public void setup() throws Exception {
        this.path = createDirectory(TEST_DIRECTORY);
        this.encryptedFileName = path.resolve(TEST_BRANDING) + "-" + TEST_VERSION + ".dar";
        this.decryptedFileName = path.resolve(TEST_BRANDING) + "-" + TEST_VERSION + ".zip";

        migratables = Collections.emptyList();
        ManagementFactory.getPlatformMBeanServer().registerMBean(mockWrapperManager,
                new ObjectName("org.tanukisoftware.wrapper:type=WrapperManager"));
        doNothing().when(mockWrapperManager).restart();
        createBrandingFile();
        createVersionFile();
        createMigrationPropsFile();
        this.configurationMigrationManager = mock(ConfigurationMigrationManager.class, Mockito.withSettings()
                .useConstructor(migratables, mockSystemService).defaultAnswer(Mockito.CALLS_REAL_METHODS));
        System.setProperty(RESTART_JVM, "true");
    }

    @After
    public void tearDown() throws Exception {
        System.getProperties().remove(RESTART_JVM);
        System.getProperties().remove(WRAPPER_KEY);
        ManagementFactory.getPlatformMBeanServer()
                .unregisterMBean(new ObjectName("org.tanukisoftware.wrapper:type=WrapperManager"));
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorWithNullConfigurationMigratablesList() {
        new ConfigurationMigrationManager(null, mockSystemService);
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorWithNullSystemService() {
        new ConfigurationMigrationManager(new ArrayList<>(), null);
    }

    @Test
    public void constructorWithoutProductBrandingFile() throws Exception {
        deleteBrandingFile();

        thrown.expect(IOError.class);
        thrown.expectMessage("Branding.txt");
        thrown.expectCause(Matchers.instanceOf(NoSuchFileException.class));

        new ConfigurationMigrationManager(new ArrayList<>(), mockSystemService);
    }

    @Test
    public void constructorWithoutProductVersionFile() throws Exception {
        deleteVersionFile();

        thrown.expect(IOError.class);
        thrown.expectMessage("Version.txt");
        thrown.expectCause(Matchers.instanceOf(NoSuchFileException.class));

        new ConfigurationMigrationManager(new ArrayList<>(), mockSystemService);
    }

    @Test
    public void constructorWithMissingProductBranding() throws Exception {
        createBrandingFile("");

        thrown.expect(IOError.class);
        thrown.expectMessage("missing product branding information");

        new ConfigurationMigrationManager(new ArrayList<>(), mockSystemService);
    }

    @Test
    public void constructorWithMissingProductVersion() throws Exception {
        createVersionFile("");

        thrown.expect(IOError.class);
        thrown.expectMessage("missing product version information");

        new ConfigurationMigrationManager(new ArrayList<>(), mockSystemService);
    }

    @Test
    public void doExportSucceeds() throws Exception {
        MigrationReport report = configurationMigrationManager.doExport(path);

        assertThat("Export was not successful", report.wasSuccessful(), is(true));
    }

    @Test
    public void doExportSucceedsWithConsumer() throws Exception {
        final Consumer<MigrationMessage> consumer = mock(Consumer.class);

        MigrationReport report = configurationMigrationManager.doExport(path, consumer);

        assertThat("Export was not successful", report.wasSuccessful(), is(true));
        verify(consumer, Mockito.atLeastOnce()).accept(Mockito.notNull());
    }

    @Test
    public void doExportSucceedsWithWarnings() throws Exception {
        createDummyExport(Paths.get(encryptedFileName));

        doAnswer(invocation -> {
            MigrationReport report = invocation.getArgument(0);
            report.record(new MigrationWarning(TEST_MESSAGE));
            return null;
        }).when(configurationMigrationManager).delegateToExportMigrationManager(any(MigrationReportImpl.class),
                any(Path.class), any(CipherUtils.class));

        MigrationReport report = configurationMigrationManager.doExport(path);

        assertThat("Export was not successful", report.wasSuccessful(), is(true));
        reportHasWarningMessage(report.warnings(), equalTo(TEST_MESSAGE));
        reportHasWarningMessage(report.warnings(), equalTo(
                "Successfully exported to file [" + encryptedFileName + "] with warnings; make sure to review."));
        verify(configurationMigrationManager).delegateToExportMigrationManager(any(MigrationReportImpl.class),
                any(Path.class), any(CipherUtils.class));
    }

    @Test(expected = IllegalArgumentException.class)
    public void doExportWithNullPath() throws Exception {
        configurationMigrationManager.doExport((Path) null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void doExportWithNullConsumer() throws Exception {
        configurationMigrationManager.doExport(path, null);
    }

    @Test
    public void doExportFailsToCreateDirectory() throws Exception {
        final Path path = ddfHome.resolve("invalid-directory");

        path.toFile().createNewFile(); // create it as a file

        MigrationReport report = configurationMigrationManager.doExport(path);

        reportHasErrorMessage(report.errors(),
                Matchers.matchesPattern("Unexpected error: unable to create directory \\["
                        + path.toString().replace("\\", "\\\\") + "\\]; .*\\."));
    }

    @Test
    public void doExportRecordsErrorForMigrationException() throws Exception {
        doThrow(new MigrationException(TEST_MESSAGE)).when(configurationMigrationManager)
                .delegateToExportMigrationManager(any(MigrationReportImpl.class), any(Path.class),
                        any(CipherUtils.class));

        MigrationReport report = configurationMigrationManager.doExport(path);

        reportHasErrorMessage(report.errors(), equalTo(TEST_MESSAGE));
        verify(configurationMigrationManager).delegateToExportMigrationManager(any(MigrationReportImpl.class),
                any(Path.class), any(CipherUtils.class));
    }

    @Test
    public void doExportRecordsErrorForIOException() throws Exception {
        doThrow(new IOException("testing")).when(configurationMigrationManager).delegateToExportMigrationManager(
                any(MigrationReportImpl.class), any(Path.class), any(CipherUtils.class));

        MigrationReport report = configurationMigrationManager.doExport(path);

        reportHasErrorMessage(report.errors(),
                equalTo("Export error: failed to close export file [" + encryptedFileName + "]; testing."));
        verify(configurationMigrationManager).delegateToExportMigrationManager(any(MigrationReportImpl.class),
                any(Path.class), any(CipherUtils.class));
    }

    @Test
    public void doExportRecordsErrorForRuntimeException() throws Exception {
        doThrow(new RuntimeException("testing.")).when(configurationMigrationManager)
                .delegateToExportMigrationManager(any(MigrationReportImpl.class), any(Path.class),
                        any(CipherUtils.class));

        MigrationReport report = configurationMigrationManager.doExport(path);

        reportHasErrorMessage(report.errors(), equalTo(
                "Unexpected internal error: failed to export to file [" + encryptedFileName + "]; testing."));
        verify(configurationMigrationManager).delegateToExportMigrationManager(any(MigrationReportImpl.class),
                any(Path.class), any(CipherUtils.class));
    }

    @Test
    public void doImportSucceedsWithKarafRestart() throws Exception {
        expectZipWithChecksum(true);
        expectImportDelegationIsSuccessful();

        MigrationReport report = configurationMigrationManager.doImport(path);

        assertThat("Import was not successful", report.wasSuccessful(), is(true));
        assertThat("Restart system property was cleared", System.getProperty(RESTART_JVM), equalTo("false"));
        assertThat(ddfBin.resolve("restart.jvm").toFile(), FileMatchers.anExistingFile());
        reportHasInfoMessage(report.infos(),
                equalTo("Successfully imported from file [" + encryptedFileName + "]."));
        reportHasInfoMessage(report.infos(), equalTo("Restarting the system for changes to take effect."));
        verify(mockSystemService).halt();
        verify(mockWrapperManager, Mockito.never()).restart();
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), Mockito.same(Collections.emptySet()));
    }

    @Test
    public void doImportSucceedsWithWrapperRestart() throws Exception {
        System.setProperty(WRAPPER_KEY, "abc");

        expectZipWithChecksum(true);
        expectImportDelegationIsSuccessful();

        // Need to actually generate a zip with a valid checksum so calling export
        configurationMigrationManager.doExport(path);

        MigrationReport report = configurationMigrationManager.doImport(path);

        assertThat("Import was not successful", report.wasSuccessful(), is(true));
        assertThat("Restart system property was not cleared", System.getProperty(RESTART_JVM), equalTo("true"));
        assertThat(ddfBin.resolve("restart.jvm").toFile(), Matchers.not(FileMatchers.anExistingFile()));
        reportHasInfoMessage(report.infos(),
                equalTo("Successfully imported from file [" + encryptedFileName + "]."));
        reportHasInfoMessage(report.infos(), equalTo("Restarting the system for changes to take effect."));
        verify(mockSystemService, Mockito.never()).halt();
        verify(mockWrapperManager).restart();
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), Mockito.same(Collections.emptySet()));
    }

    @Test
    public void doImportSucceedsWithConsumer() throws Exception {
        final Consumer<MigrationMessage> consumer = mock(Consumer.class);

        expectZipWithChecksum(true);
        expectImportDelegationIsSuccessful();

        MigrationReport report = configurationMigrationManager.doImport(path, consumer);

        assertThat("Import was not successful", report.wasSuccessful(), is(true));
        assertThat("Restart system property was cleared", System.getProperty(RESTART_JVM), equalTo("false"));
        assertThat(ddfBin.resolve("restart.jvm").toFile(), FileMatchers.anExistingFile());
        reportHasInfoMessage(report.infos(),
                equalTo("Successfully imported from file [" + encryptedFileName + "]."));
        reportHasInfoMessage(report.infos(), equalTo("Restarting the system for changes to take effect."));
        verify(mockSystemService).halt();
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), Mockito.same(Collections.emptySet()));
        verify(mockWrapperManager, Mockito.never()).restart();
        verify(consumer, Mockito.atLeastOnce()).accept(Mockito.notNull());
    }

    @Test
    public void doImportSucceedsWithConsumerAndMandatorySet() throws Exception {
        final Set<String> mandatoryMgiratables = new HashSet<>();
        final Path path = ddfHome.resolve(TEST_DIRECTORY);
        final Consumer<MigrationMessage> consumer = mock(Consumer.class);

        // Need to actually generate a zip with a valid checksum so calling export
        configurationMigrationManager.doExport(path);

        expectImportDelegationIsSuccessful();

        MigrationReport report = configurationMigrationManager.doImport(path, mandatoryMgiratables, consumer);

        assertThat("Import was not successful", report.wasSuccessful(), is(true));
        assertThat("Restart system property was cleared", System.getProperty(RESTART_JVM), equalTo("false"));
        assertThat(ddfBin.resolve("restart.jvm").toFile(), FileMatchers.anExistingFile());
        reportHasInfoMessage(report.infos(), equalTo(
                "Successfully imported from file [" + path.resolve(TEST_BRANDING) + "-" + TEST_VERSION + ".dar]."));
        reportHasInfoMessage(report.infos(), equalTo("Restarting the system for changes to take effect."));
        verify(mockSystemService).halt();
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), Mockito.same(mandatoryMgiratables));
        verify(mockWrapperManager, Mockito.never()).restart();
        verify(consumer, Mockito.atLeastOnce()).accept(Mockito.notNull());
    }

    @Test
    public void doImportSucceedsWithWarnings() throws Exception {
        expectZipWithChecksum(true);

        doAnswer(invocation -> {
            MigrationReport report = invocation.getArgument(0);
            report.record(new MigrationWarning(TEST_MESSAGE));
            return null;
        }).when(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), any(Set.class));

        MigrationReport report = configurationMigrationManager.doImport(path);

        assertThat("Import was not successful", report.wasSuccessful(), is(true));
        reportHasWarningMessage(report.warnings(), equalTo(TEST_MESSAGE));
        reportHasWarningMessage(report.warnings(), equalTo(
                "Successfully imported from file [" + encryptedFileName + "] with warnings; make sure to review."));
        verify(mockWrapperManager, Mockito.never()).restart();
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), any(Set.class));
    }

    @Test
    public void doImportSucceedsWithKarafAndFailsToReboot() throws Exception {
        expectZipWithChecksum(true);
        expectImportDelegationIsSuccessful();
        doThrow(Exception.class).when(mockSystemService).halt();

        MigrationReport report = configurationMigrationManager.doImport(path);

        assertThat("Import was successful", report.wasSuccessful(), is(true));
        assertThat("Restart system property was cleared", System.getProperty(RESTART_JVM), equalTo("false"));
        assertThat(ddfBin.resolve("restart.jvm").toFile(), FileMatchers.anExistingFile());
        reportHasInfoMessage(report.infos(),
                equalTo("Successfully imported from file [" + encryptedFileName + "]."));
        reportHasInfoMessage(report.infos(), equalTo("Please restart the system for changes to take effect."));
        verify(mockSystemService).halt();
        verify(mockWrapperManager, Mockito.never()).restart();
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), any(Set.class));
    }

    @Test
    public void doImportSucceedsWithWrapperRestartAndFailsToReboot() throws Exception {
        System.setProperty(WRAPPER_KEY, "abc");

        doThrow(new MBeanException(null)).when(mockWrapperManager).restart();
        expectZipWithChecksum(true);
        expectImportDelegationIsSuccessful();

        MigrationReport report = configurationMigrationManager.doImport(path);

        assertThat("Import was successful", report.wasSuccessful(), is(true));
        assertThat("Restart system property was not cleared", System.getProperty(RESTART_JVM), equalTo("true"));
        assertThat(ddfBin.resolve("restart.jvm").toFile(), Matchers.not(FileMatchers.anExistingFile()));
        reportHasInfoMessage(report.infos(),
                equalTo("Successfully imported from file [" + encryptedFileName + "]."));
        reportHasInfoMessage(report.infos(), equalTo("Please restart the system for changes to take effect."));
        verify(mockSystemService, Mockito.never()).halt();
        verify(mockWrapperManager).restart();
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), any(Set.class));
    }

    @Test(expected = IllegalArgumentException.class)
    public void doImportWithNullPath() throws Exception {
        configurationMigrationManager.doImport((Path) null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void doImportWithNullConsumer() throws Exception {
        configurationMigrationManager.doImport(path, null);
    }

    @Test
    public void doImportRecordsErrorForMigrationException() throws Exception {
        expectZipWithChecksum(true);
        doThrow(new MigrationException(TEST_MESSAGE)).when(configurationMigrationManager)
                .delegateToImportMigrationManager(any(MigrationReportImpl.class), any(MigrationZipFile.class),
                        any(Set.class));

        MigrationReport report = configurationMigrationManager.doImport(path);

        reportHasErrorMessage(report.errors(), equalTo(TEST_MESSAGE));
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), any(Set.class));
        verify(mockWrapperManager, Mockito.never()).restart();
        verifyZeroInteractions(mockSystemService);
    }

    @Test
    public void doImportRecordsErrorForRuntimeException() throws Exception {
        expectZipWithChecksum(true);
        doThrow(new RuntimeException("testing")).when(configurationMigrationManager)
                .delegateToImportMigrationManager(any(MigrationReportImpl.class), any(MigrationZipFile.class),
                        any(Set.class));

        MigrationReport report = configurationMigrationManager.doImport(path);

        reportHasErrorMessage(report.errors(), equalTo(
                "Unexpected internal error: failed to import from file [" + encryptedFileName + "]; testing."));
        verify(configurationMigrationManager).delegateToImportMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), any(Set.class));
        verify(mockWrapperManager, Mockito.never()).restart();
        verifyZeroInteractions(mockSystemService);
    }

    @Test
    public void testDoImportRecordsMigrationExceptionWithInvalidChecksum() throws Exception {
        expectZipWithChecksum(false);

        configurationMigrationManager.doExport(path);

        MigrationReport report = configurationMigrationManager.doImport(path);

        assertThat(report.wasSuccessful(), equalTo(false));
        reportHasErrorMessage(report.errors(),
                equalTo("Import error: incorrect checksum for export file [" + encryptedFileName + "]."));
    }

    @Test
    public void doDecryptSucceeds() throws Exception {
        expectZipWithChecksum(true);
        expectDecryptDelegationIsSuccessful();

        final MigrationReport report = configurationMigrationManager.doDecrypt(path);

        assertThat("Decrypt was not successful", report.wasSuccessful(), is(true));
        reportHasInfoMessage(report.infos(),
                equalTo("Successfully decrypted file [" + encryptedFileName + "] to [" + decryptedFileName + "]."));
        verify(configurationMigrationManager).delegateToDecryptMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), eq(Paths.get(decryptedFileName)));
        verifyZipEncryptedFile();
    }

    @Test
    public void doDecryptSucceedsWithConsumer() throws Exception {
        final Consumer<MigrationMessage> consumer = mock(Consumer.class);

        // Need to actually generate a zip with a valid checksum so calling export
        configurationMigrationManager.doExport(path);

        expectDecryptDelegationIsSuccessful();

        MigrationReport report = configurationMigrationManager.doDecrypt(path, consumer);

        assertThat("Decrypt was not successful", report.wasSuccessful(), is(true));
        reportHasInfoMessage(report.infos(),
                equalTo("Successfully decrypted file [" + encryptedFileName + "] to [" + decryptedFileName + "]."));
        verify(configurationMigrationManager).delegateToDecryptMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), eq(Paths.get(decryptedFileName)));
        verify(consumer, Mockito.atLeastOnce()).accept(Mockito.notNull());
        verifyZipEncryptedFile();
    }

    @Test
    public void doDecryptSucceedsWithWarnings() throws Exception {
        doAnswer(invocation -> {
            MigrationReport report = invocation.getArgument(0);
            report.record(new MigrationWarning(TEST_MESSAGE));
            return null;
        }).when(configurationMigrationManager).delegateToDecryptMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), any(Path.class));

        // Need to actually generate a zip with a valid checksum so calling export
        configurationMigrationManager.doExport(path);

        MigrationReport report = configurationMigrationManager.doDecrypt(path);

        assertThat("Decrypt was not successful", report.wasSuccessful(), is(true));
        reportHasWarningMessage(report.warnings(), equalTo(TEST_MESSAGE));
        reportHasWarningMessage(report.warnings(), equalTo("Successfully decrypted file [" + encryptedFileName
                + "] to [" + decryptedFileName + "] with warnings; make sure to review."));
        verify(configurationMigrationManager).delegateToDecryptMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), eq(Paths.get(decryptedFileName)));
        verifyZipEncryptedFile();
    }

    @Test(expected = IllegalArgumentException.class)
    public void doDecryptWithNullPath() throws Exception {
        configurationMigrationManager.doDecrypt((Path) null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void doDecryptWithNullConsumer() throws Exception {
        configurationMigrationManager.doDecrypt(path, null);
    }

    @Test
    public void doDecryptRecordsErrorForMigrationException() throws Exception {
        // Need to actually generate a zip with a valid checksum so calling export
        configurationMigrationManager.doExport(path);

        doThrow(new MigrationException(TEST_MESSAGE)).when(configurationMigrationManager)
                .delegateToDecryptMigrationManager(any(MigrationReportImpl.class), any(MigrationZipFile.class),
                        any(Path.class));

        MigrationReport report = configurationMigrationManager.doDecrypt(path);

        reportHasErrorMessage(report.errors(), equalTo(TEST_MESSAGE));
        verify(configurationMigrationManager).delegateToDecryptMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), eq(Paths.get(decryptedFileName)));
        verifyZipEncryptedFile();
        verifyZeroInteractions(mockSystemService);
    }

    @Test
    public void doDecryptRecordsErrorForRuntimeException() throws Exception {
        // Need to actually generate a zip with a valid checksum so calling export
        configurationMigrationManager.doExport(path);

        doThrow(new RuntimeException("testing")).when(configurationMigrationManager)
                .delegateToDecryptMigrationManager(any(MigrationReportImpl.class), any(MigrationZipFile.class),
                        any(Path.class));

        MigrationReport report = configurationMigrationManager.doDecrypt(path);

        reportHasErrorMessage(report.errors(),
                equalTo("Unexpected internal error: failed to decrypt file [" + encryptedFileName + "]; testing."));
        verify(configurationMigrationManager).delegateToDecryptMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), eq(Paths.get(decryptedFileName)));
        verifyZipEncryptedFile();
        verifyZeroInteractions(mockSystemService);
    }

    @Test
    public void doDecryptRecordsErrorForIOException() throws Exception {
        // Need to actually generate a zip with a valid checksum so calling export
        configurationMigrationManager.doExport(path);

        doThrow(new IOException("testing")).when(configurationMigrationManager).delegateToDecryptMigrationManager(
                any(MigrationReportImpl.class), any(MigrationZipFile.class), any(Path.class));

        MigrationReport report = configurationMigrationManager.doDecrypt(path);

        reportHasErrorMessage(report.errors(),
                equalTo("Decrypt error: failed to close decrypted file [" + decryptedFileName + "]; testing."));
        verify(configurationMigrationManager).delegateToDecryptMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), eq(Paths.get(decryptedFileName)));
        verifyZipEncryptedFile();
        verifyZeroInteractions(mockSystemService);
    }

    @Test
    public void doDecryptRecordsErrorForSecurityException() throws Exception {
        // Need to actually generate a zip with a valid checksum so calling export
        configurationMigrationManager.doExport(path);

        doThrow(new SecurityException("testing")).when(configurationMigrationManager)
                .delegateToDecryptMigrationManager(any(MigrationReportImpl.class), any(MigrationZipFile.class),
                        any(Path.class));

        MigrationReport report = configurationMigrationManager.doDecrypt(path);

        reportHasErrorMessage(report.errors(),
                equalTo("Decrypt security error: failed to decrypt file [" + encryptedFileName + "]; testing."));
        verify(configurationMigrationManager).delegateToDecryptMigrationManager(any(MigrationReportImpl.class),
                any(MigrationZipFile.class), eq(Paths.get(decryptedFileName)));
        verifyZipEncryptedFile();
        verifyZeroInteractions(mockSystemService);
    }

    @Test
    public void testDoDecryptRecordsMigrationExceptionWithInvalidChecksum() throws Exception {
        expectZipWithChecksum(false);
        final MigrationReport report = configurationMigrationManager.doDecrypt(path);

        assertThat(report.wasSuccessful(), equalTo(false));
        reportHasErrorMessage(report.errors(),
                equalTo("Decrypt error: incorrect checksum for export file [" + encryptedFileName + "]."));
    }

    private void expectImportDelegationIsSuccessful() {
        doNothing().when(configurationMigrationManager).delegateToImportMigrationManager(
                any(MigrationReportImpl.class), any(MigrationZipFile.class), any(Set.class));
    }

    private void expectDecryptDelegationIsSuccessful() throws IOException {
        doNothing().when(configurationMigrationManager).delegateToDecryptMigrationManager(
                any(MigrationReportImpl.class), any(MigrationZipFile.class), any(Path.class));
    }

    private void expectZipWithChecksum(boolean valid) {
        final MigrationZipFile zip = mock(MigrationZipFile.class);

        doReturn(zip).when(configurationMigrationManager).newZipFileFor(Mockito.notNull());
        doReturn(valid).when(zip).isValidChecksum();
        doReturn(Paths.get(encryptedFileName)).when(zip).getZipPath();
    }

    private void verifyZipEncryptedFile() {
        verify(configurationMigrationManager).newZipFileFor(path);
    }

    private void reportHasInfoMessage(Stream<MigrationInformation> infos, Matcher<String> matcher) {
        final List<MigrationInformation> is = infos.collect(Collectors.toList());
        final long count = is.stream().filter((w) -> matcher.matches(w.getMessage())).count();
        final Description d = new StringDescription();

        matcher.describeTo(d);
        assertThat("There are " + count + " matching info message(s) with " + d
                + " in the migration report.\nWarnings are: " + is.stream().map(MigrationInformation::getMessage)
                        .collect(Collectors.joining("\",\n\t\"", "[\n\t\"", "\"\n]")),
                count, equalTo(1L));
    }

    private void reportHasWarningMessage(Stream<MigrationWarning> warnings, Matcher<String> matcher) {
        final List<MigrationWarning> ws = warnings.collect(Collectors.toList());
        final long count = ws.stream().filter((w) -> matcher.matches(w.getMessage())).count();
        final Description d = new StringDescription();

        matcher.describeTo(d);
        assertThat("There are " + count + " matching warning(s) with " + d
                + " in the migration report.\nWarnings are: " + ws.stream().map(MigrationWarning::getMessage)
                        .collect(Collectors.joining("\",\n\t\"", "[\n\t\"", "\"\n]")),
                count, equalTo(1L));
    }

    private void reportHasErrorMessage(Stream<MigrationException> errors, Matcher<String> matcher) {
        final List<MigrationException> es = errors.collect(Collectors.toList());
        final long count = es.stream().filter((e) -> matcher.matches(e.getMessage())).count();
        final Description d = new StringDescription();

        matcher.describeTo(d);

        assertThat("There are " + count + " matching error(s) with " + d + " in the migration report.\nErrors are: "
                + es.stream().map(MigrationException::getMessage)
                        .collect(Collectors.joining("\",\n\t\"", "[\n\t\"", "\"\n]")),
                count, equalTo(1L));
    }

    private void createBrandingFile(String branding) throws IOException {
        final File brandingFile = new File(ddfHome.resolve("Branding.txt").toString());

        brandingFile.createNewFile();
        Files.write(brandingFile.toPath(), branding.getBytes(), StandardOpenOption.CREATE,
                StandardOpenOption.TRUNCATE_EXISTING);
    }

    private void deleteBrandingFile() {
        new File(ddfHome.resolve("Branding.txt").toString()).delete();
    }

    private void createBrandingFile() throws IOException {
        createBrandingFile(TEST_BRANDING);
    }

    private void createVersionFile(String version) throws IOException {
        File versionFile = new File(ddfHome.resolve("Version.txt").toString());
        versionFile.createNewFile();
        Files.write(versionFile.toPath(), version.getBytes(), StandardOpenOption.CREATE,
                StandardOpenOption.TRUNCATE_EXISTING);
    }

    private void createVersionFile() throws IOException {
        createVersionFile(TEST_VERSION);
    }

    private void createMigrationPropsFile(String contents) throws IOException {
        Path migrationPropsPath = ddfHome.resolve(Paths.get("etc", "migration.properties"));
        Files.createDirectories(migrationPropsPath.getParent());
        Files.createFile(migrationPropsPath);
        Files.write(migrationPropsPath, contents.getBytes(), StandardOpenOption.CREATE,
                StandardOpenOption.TRUNCATE_EXISTING);
    }

    private void createMigrationPropsFile() throws IOException {
        createMigrationPropsFile("supported.versions=" + TEST_VERSION);
    }

    private void deleteVersionFile() {
        new File(ddfHome.resolve("Version.txt").toString()).delete();
    }

    public static interface WrapperManagerMXBean {

        public void restart() throws MBeanException;
    }

    private void createDummyExport(Path exportPath)
            throws IOException, NoSuchPaddingException, NoSuchAlgorithmException {
        FileUtils.forceMkdirParent(exportPath.toFile());
        ZipOutputStream zos = new ZipOutputStream(
                new BufferedOutputStream(new FileOutputStream(exportPath.toFile())));
        CipherUtils cipherUtils = new CipherUtils(exportPath);
        zos.close();
        cipherUtils.createZipChecksumFile();
    }
}