fr.duminy.jbackup.core.ConfigurationManagerTest.java Source code

Java tutorial

Introduction

Here is the source code for fr.duminy.jbackup.core.ConfigurationManagerTest.java

Source

/**
 * JBackup is a software managing backups.
 *
 * Copyright (C) 2013-2014 Fabien DUMINY (fabien [dot] duminy [at] webmails [dot] com)
 *
 * JBackup is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * JBackup 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 */
package fr.duminy.jbackup.core;

import fr.duminy.jbackup.core.archive.zip.ZipArchiveFactory;
import fr.duminy.jbackup.core.archive.zip.ZipArchiveFactoryTest;
import fr.duminy.jbackup.core.util.LogRule;
import org.apache.commons.beanutils.PropertyUtils;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.InvalidNameException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.util.*;

import static fr.duminy.jbackup.core.TestUtils.createFile;
import static java.lang.Thread.sleep;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

/**
 * Tests for class {@link fr.duminy.jbackup.core.ConfigurationManager}.
 */
public class ConfigurationManagerTest {
    private static final Logger LOG = LoggerFactory.getLogger(ConfigurationManagerTest.class);

    private static final String TARGET_DIRECTORY = "aDirectory";
    private static final String CONFIG1 = "configOne";
    private static final String CONFIG2 = "configTwo";
    private static final String CONFIG_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
            + "<backupConfiguration xmlVersion=\"1\">\n" + "    <archiveFactory>"
            + ZipArchiveFactory.class.getName() + "</archiveFactory>\n" + "    <name>" + CONFIG1 + "</name>\n"
            + "    <relativeEntries>true</relativeEntries>\n" + "    <sources>\n"
            + generateSourceXml("        ", "aDirFilter", "aFileFilter", "aSource")
            + generateSourceXml("        ", null, null, "aSource2")
            + generateSourceXml("        ", "anotherDirFilter", "anotherFileFilter", "anotherSource")
            + "    </sources>\n" + "    <targetDirectory>" + TARGET_DIRECTORY + "</targetDirectory>\n"
            + "</backupConfiguration>\n";
    private static final String CONFIG_XML2 = CONFIG_XML.replace("<name>" + CONFIG1 + "</name>",
            "<name>" + CONFIG2 + "</name>");

    private static String generateSourceXml(String indent, String dirFilter, String fileFilter,
            String sourceDirectory) {
        return indent + "<source>\n"
                + ((dirFilter == null) ? "" : indent + "    <dirFilter>" + dirFilter + "</dirFilter>\n")
                + ((fileFilter == null) ? "" : indent + "    <fileFilter>" + fileFilter + "</fileFilter>\n")
                + ((sourceDirectory == null) ? ""
                        : indent + "    <path>" + Paths.get(sourceDirectory).toAbsolutePath() + "</path>\n")
                + indent + "</source>\n";
    }

    private Path configDir;
    private ConfigurationManager manager;

    @Rule
    public final LogRule logRule = new LogRule();

    @Rule
    public final TemporaryFolder tempFolder = new TemporaryFolder();

    @Before
    public void setUp() throws Exception {
        configDir = tempFolder.newFolder().toPath();
        manager = new ConfigurationManager(configDir);
    }

    @Test(expected = NullPointerException.class)
    public void testInit_nullDirectory() throws Exception {
        new ConfigurationManager(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInit_notWritableDirectory() throws Exception {
        Path dir = tempFolder.newFolder().toPath();
        dir.toFile().setWritable(false);
        new ConfigurationManager(dir);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInit_notADirectory() throws Exception {
        Path dir = tempFolder.newFile().toPath();
        new ConfigurationManager(dir);
    }

    @Test
    public void testInit_nonExistingDirectory() throws Exception {
        Path dir = tempFolder.newFolder().toPath();
        Files.delete(dir);
        assertThat(Files.exists(dir)).as("directory exists").isFalse();

        new ConfigurationManager(dir);

        assertThat(Files.exists(dir)).as("directory exists").isTrue();
    }

    @Test
    public void testGetBackupConfigurations() throws Exception {
        writeConfigFile(true); //config1=true
        writeConfigFile(false); //config1=false => config2

        Collection<BackupConfiguration> configs = manager.getBackupConfigurations();

        assertThat(configs).extracting("name").containsOnlyOnce(CONFIG1, CONFIG2);
    }

    @Test
    public void testRemoveBackupConfiguration_sameName() throws Exception {
        testRemoveBackupConfiguration(false, false);
    }

    @Test
    public void testRemoveBackupConfiguration_sameInstance() throws Exception {
        testRemoveBackupConfiguration(true, false);
    }

    @Test
    public void testRemoveBackupConfiguration_unknownName() throws Exception {
        testRemoveBackupConfiguration(false, true);
    }

    private void testRemoveBackupConfiguration(boolean sameInstance, boolean unknownName) throws Exception {
        BackupConfiguration config = createConfiguration();
        manager.addBackupConfiguration(config);

        BackupConfiguration configToRemove = config;
        if (!sameInstance) {
            configToRemove = createConfiguration();
            if (unknownName) {
                configToRemove.setName("unknownName");
            }
        }
        manager.removeBackupConfiguration(configToRemove);

        if (unknownName) {
            ConfigurationManagerAssert.assertThat(manager).hasBackupConfigurations(config); //TODO replace by hasOnlyOnceBackupConfigurations(config);

            existsRW(configFileFor(config));
            assertThat(Files.exists(configFileFor(configToRemove))).as("configFileToRemove exists").isFalse();
        } else {
            ConfigurationManagerAssert.assertThat(manager).hasNoBackupConfigurations();

            assertThat(Files.exists(configFileFor(config))).as("configFile exists").isFalse();
            assertThat(Files.exists(configFileFor(configToRemove))).as("configFileToRemove exists").isFalse();
        }
    }

    @Test
    public void testAddBackupConfiguration() throws Exception {
        BackupConfiguration expectedConfiguration = createConfiguration();

        manager.addBackupConfiguration(expectedConfiguration);

        Collection<BackupConfiguration> configs = manager.getBackupConfigurations();
        assertThat(configs).hasSize(1);
        BackupConfiguration actualConfiguration = configs.iterator().next();
        BackupConfigurationAssert.assertThat(actualConfiguration).isEqualTo(expectedConfiguration);
        existsRW(configFileFor(expectedConfiguration));
    }

    @Test
    public void testSetBackupConfiguration() throws Exception {
        BackupConfiguration oldConfig = createConfiguration("oldName");
        BackupConfiguration newConfig = createConfiguration("newName");
        manager.addBackupConfiguration(oldConfig);

        manager.setBackupConfiguration(0, newConfig);

        Collection<BackupConfiguration> configs = manager.getBackupConfigurations();
        assertThat(configs).hasSize(1);
        BackupConfiguration actualConfiguration = configs.iterator().next();
        BackupConfigurationAssert.assertThat(actualConfiguration).isNotSameAs(oldConfig);
        BackupConfigurationAssert.assertThat(actualConfiguration).isSameAs(newConfig);
        existsRW(configFileFor(oldConfig), false);
        existsRW(configFileFor(newConfig));
    }

    private void existsRW(Path configFile) {
        existsRW(configFile, true);
    }

    private void existsRW(Path configFile, boolean existsRW) {
        assertThat(Files.exists(configFile)).as("configFile exists").isEqualTo(existsRW);
        assertThat(Files.isReadable(configFile)).as("configFile is readable").isEqualTo(existsRW);
        assertThat(Files.isWritable(configFile)).as("configFile is writable").isEqualTo(existsRW);
    }

    @Test(expected = DuplicateNameException.class)
    public void testAddBackupConfiguration_duplicateName() throws Exception {
        manager.addBackupConfiguration(createConfiguration());
        manager.addBackupConfiguration(createConfiguration());
    }

    @Test
    public void testAddBackupConfiguration_invalidNames() throws Exception {
        String[] names = { null, "", " ", "a.b", "a b" };

        Set<String> failingNames = new HashSet<>();
        for (String name : names) {
            try {
                manager.addBackupConfiguration(createConfiguration(name));
                failingNames.add(name);
            } catch (InvalidNameException ine) {
                // ok
            }
        }

        assertThat(failingNames).as("failingNames").isEmpty();
    }

    @Test
    public void testLoadAllConfigurations() throws Exception {
        // prepare mock
        Path validXmlConfigFile1 = writeConfigFile(true); //config1=true
        Path validXmlConfigFile2 = writeConfigFile(false); //config1=false => config2

        ConfigurationManager mock = spy(manager);
        doCallRealMethod().when(mock).loadAllConfigurations();

        // test
        mock.loadAllConfigurations();

        // verify
        verify(mock, times(1)).loadBackupConfiguration(eq(validXmlConfigFile1));
        verify(mock, times(1)).loadBackupConfiguration(eq(validXmlConfigFile2));
        verify(mock, times(1)).loadAllConfigurations();
        verifyNoMoreInteractions(mock);
        assertThat(mock.getBackupConfigurations()).extracting("name").as("fully valid config files")
                .containsOnlyOnce(CONFIG1, CONFIG2);
    }

    @Test
    public void testLoadAllConfigurations_nonXmlFile() throws Exception {
        // prepare mock
        Path notAnXmlFile = configDir.resolve("notAnXmlFile");
        createFile(notAnXmlFile, "");

        ConfigurationManager mock = spy(manager);
        doCallRealMethod().when(mock).loadAllConfigurations();

        // test
        mock.loadAllConfigurations();

        // verify
        verify(mock, times(1)).loadAllConfigurations();
        verify(mock, never()).loadBackupConfiguration(eq(notAnXmlFile));
        verifyNoMoreInteractions(mock);
        assertThat(mock.getBackupConfigurations()).isEmpty();
    }

    @Test
    public void testLoadAllConfigurations_wrongXmlConfigFile() throws Exception {
        // prepare mock
        final Path wrongXmlConfigFile = configDir.resolve("wrongXmlConfigFile.xml");
        createFile(wrongXmlConfigFile, "<root></root>");

        ConfigurationManager mock = spy(manager);
        doCallRealMethod().when(mock).loadAllConfigurations();
        final List<Exception> errors = new ArrayList<>();
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                Path file = (Path) invocation.getArguments()[0];
                if (file.equals(wrongXmlConfigFile)) {
                    try {
                        return invocation.callRealMethod();
                    } catch (Exception e) {
                        errors.add(e);
                        throw e;
                    }
                } else {
                    return invocation.callRealMethod();
                }
            }
        }).when(mock).loadBackupConfiguration(any(Path.class));

        // test
        LOG.warn("**************************************************************");
        LOG.warn("***** The following logged ERROR is expected by the test *****");
        LOG.warn("**************************************************************");
        mock.loadAllConfigurations();

        // verify
        verify(mock, times(1)).loadBackupConfiguration(eq(wrongXmlConfigFile));
        verify(mock, times(1)).loadAllConfigurations();
        verifyNoMoreInteractions(mock);
        assertThat(errors).as("number of invalid xml files").hasSize(1);
        ConfigurationManagerAssert.assertThat(mock).hasNoBackupConfigurations();
        LOG.warn("**************************************************************");
        LOG.warn("*************** End of expected ERROR in test ***************");
        LOG.warn("**************************************************************");
    }

    @Test
    public void testLoadAllConfigurations_wrongConfigName() throws Exception {
        // prepare mock
        final Path wrongConfigName = configDir.resolve("config4.xml");
        String otherTargetDirectory = TARGET_DIRECTORY + '2';
        createFile(wrongConfigName, CONFIG_XML2.replace(TARGET_DIRECTORY, otherTargetDirectory));

        ConfigurationManager mock = spy(manager);
        doCallRealMethod().when(mock).loadAllConfigurations();

        // test
        mock.loadAllConfigurations();

        // verify
        verify(mock, times(1)).loadBackupConfiguration(eq(wrongConfigName));
        verify(mock, times(1)).loadAllConfigurations();
        verifyNoMoreInteractions(mock);
        ConfigurationManagerAssert.assertThat(mock).hasNoBackupConfigurations();
    }

    @Test
    public void testLoadBackupConfiguration() throws Exception {
        BackupConfiguration expectedConfiguration = createConfiguration();

        Path input = tempFolder.newFile().toPath();
        createFile(input, CONFIG_XML);
        BackupConfiguration actualConfiguration = manager.loadBackupConfiguration(input);

        assertAreEquals(expectedConfiguration, actualConfiguration);
    }

    @Test
    public void testSaveRenamedBackupConfiguration() throws Exception {
        BackupConfiguration config = createConfiguration();

        String oldName = config.getName();
        Path output1 = manager.saveBackupConfiguration(config);
        assertThat(Files.exists(output1)).as("output1 exists").isTrue();

        config.setName("NewName");

        Path output2 = manager.saveRenamedBackupConfiguration(oldName, config);
        assertThat(Files.exists(output1)).as("output1 exists").isFalse();
        assertThat(Files.exists(output2)).as("output2 exists").isTrue();
    }

    @Test
    public void testSaveBackupConfiguration_newConfig() throws Exception {
        testSaveBackupConfiguration(createConfiguration());
    }

    @Test
    public void testSaveBackupConfiguration_updateConfig() throws Exception {
        BackupConfiguration config = createConfiguration();

        Path output = testSaveBackupConfiguration(config);
        FileTime t0 = Files.getLastModifiedTime(output);

        sleep(1000);

        output = testSaveBackupConfiguration(config);
        FileTime t1 = Files.getLastModifiedTime(output);

        assertThat(t1.compareTo(t0) >= 0).as("t1 > t0").isTrue();
    }

    private Path testSaveBackupConfiguration(BackupConfiguration config) throws Exception {
        Path output = manager.saveBackupConfiguration(config);

        assertThat(output.getParent()).isEqualTo(configDir);
        Assertions.assertThat(output.toFile()).hasContent(CONFIG_XML);

        return output;
    }

    @Test
    public void testGetLatestArchive_noArchive() throws IOException {
        BackupConfiguration config = createConfiguration("config", tempFolder.newFolder().toPath());

        Path configFile = ConfigurationManager.getLatestArchive(config);

        assertThat(configFile).isNull();
    }

    @Test
    public void testGetLatestArchive_oneArchive() throws Exception {
        initAndGetLatestArchive(1);
    }

    @Test
    public void testGetLatestArchive_twoArchives() throws Exception {
        initAndGetLatestArchive(2);
    }

    private void initAndGetLatestArchive(int nbConfigurations) throws Exception {
        BackupConfiguration config = createConfiguration("config", tempFolder.newFolder().toPath());
        Path[] files = new Path[nbConfigurations];
        for (int i = 0; i < nbConfigurations; i++) {
            Path file = Paths.get(config.getTargetDirectory()).resolve("file" + i);
            Files.copy(ZipArchiveFactoryTest.getArchive(), file);
            Thread.sleep(1000);
            files[i] = file;
        }

        Path configFile = ConfigurationManager.getLatestArchive(config);

        assertThat(configFile).isEqualTo(files[files.length - 1]);
    }

    public static BackupConfiguration createConfiguration() {
        return createConfiguration(CONFIG1);
    }

    public static BackupConfiguration createConfiguration(String configName) {
        return createConfiguration(configName, null);
    }

    public static BackupConfiguration createConfiguration(String configName, Path targetDirectory) {
        final BackupConfiguration config = new BackupConfiguration();

        config.setName(configName);
        config.setArchiveFactory(ZipArchiveFactory.INSTANCE);
        Path targetPath = (targetDirectory == null) ? Paths.get(TARGET_DIRECTORY)
                : targetDirectory.toAbsolutePath();
        config.setTargetDirectory(targetPath.toString());
        config.addSource(Paths.get("aSource").toAbsolutePath(), "aDirFilter", "aFileFilter");
        config.addSource(Paths.get("aSource2").toAbsolutePath());
        config.addSource(Paths.get("anotherSource").toAbsolutePath(), "anotherDirFilter", "anotherFileFilter");

        return config;
    }

    private Path configFileFor(BackupConfiguration config) {
        return manager.configFileFor(config);
    }

    private Path writeConfigFile(boolean config1) throws IOException {
        Path configFile = configDir.resolve((config1 ? CONFIG1 : CONFIG2) + ".xml");
        String configXML = config1 ? CONFIG_XML : CONFIG_XML2;
        createFile(configFile, configXML);
        assertThat(Files.size(configFile)).as("config file size").isEqualTo(configXML.getBytes().length);
        return configFile;
    }

    public static void assertAreEquals(BackupConfiguration expected, BackupConfiguration actual) {
        Assertions.assertThat(describe(actual)).isEqualTo(describe(expected));
    }

    private static Map describe(BackupConfiguration config) {
        Map properties;
        try {
            properties = PropertyUtils.describe(config);
        } catch (Exception e) {
            LOG.error("unable to extract properties from configuration", e);
            properties = Collections.EMPTY_MAP;
        }

        properties.remove("class");
        properties.put("archiveFactory", config.getArchiveFactory().getClass().getName());
        List<BackupConfiguration.Source> sources = (List<BackupConfiguration.Source>) properties.remove("sources");
        properties.put("sources.size", sources.size());
        for (int i = 0; i < sources.size(); i++) {
            Map sourceProperties = null;
            try {
                sourceProperties = PropertyUtils.describe(sources.get(i));
            } catch (Exception e) {
                LOG.error("unable to extract source #" + i, e);
            }
            sourceProperties.remove("class");
            properties.put("sources[" + i + "]", sourceProperties);
        }
        return properties;
    }
}