/*
 * 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.maven.plugins.assembly.archive.phase;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Model;
import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
import org.apache.maven.plugins.assembly.InvalidAssemblerConfigurationException;
import org.apache.maven.plugins.assembly.archive.ArchiveCreationException;
import org.apache.maven.plugins.assembly.archive.DefaultAssemblyArchiverTest;
import org.apache.maven.plugins.assembly.artifact.DependencyResolver;
import org.apache.maven.plugins.assembly.model.Assembly;
import org.apache.maven.plugins.assembly.model.FileSet;
import org.apache.maven.plugins.assembly.model.ModuleBinaries;
import org.apache.maven.plugins.assembly.model.ModuleSet;
import org.apache.maven.plugins.assembly.model.ModuleSources;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.codehaus.plexus.archiver.Archiver;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.Collections.singleton;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ModuleSetAssemblyPhaseTest {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    private ModuleSetAssemblyPhase phase;

    private DependencyResolver dependencyResolver;

    @Before
    public void setUp() {
        ProjectBuilder projectBuilder = mock(ProjectBuilder.class);
        this.dependencyResolver = mock(DependencyResolver.class);

        this.phase = new ModuleSetAssemblyPhase(projectBuilder, dependencyResolver);
    }

    @Test
    public void testIsDeprecatedModuleSourcesConfigPresent_ShouldCatchOutputDir() {
        final ModuleSources sources = new ModuleSources();
        sources.setOutputDirectory("outdir");

        assertTrue(this.phase.isDeprecatedModuleSourcesConfigPresent(sources));
    }

    @Test
    public void testIsDeprecatedModuleSourcesConfigPresent_ShouldCatchInclude() {
        final ModuleSources sources = new ModuleSources();
        sources.addInclude("**/included.txt");

        assertTrue(this.phase.isDeprecatedModuleSourcesConfigPresent(sources));
    }

    @Test
    public void testIsDeprecatedModuleSourcesConfigPresent_ShouldCatchExclude() {
        final ModuleSources sources = new ModuleSources();
        sources.addExclude("**/excluded.txt");

        assertTrue(this.phase.isDeprecatedModuleSourcesConfigPresent(sources));
    }

    @Test
    public void testIsDeprecatedModuleSourcesConfigPresent_ShouldNotCatchFileMode() {
        final ModuleSources sources = new ModuleSources();
        sources.setFileMode("777");

        assertFalse(this.phase.isDeprecatedModuleSourcesConfigPresent(sources));
    }

    @Test
    public void testIsDeprecatedModuleSourcesConfigPresent_ShouldNotCatchDirMode() {
        final ModuleSources sources = new ModuleSources();
        sources.setDirectoryMode("777");

        assertFalse(this.phase.isDeprecatedModuleSourcesConfigPresent(sources));
    }

    @Test
    public void testCreateFileSet_ShouldUseModuleDirOnlyWhenOutDirIsNull() throws Exception {
        final Model model = new Model();
        model.setArtifactId("artifact");

        final MavenProject project = new MavenProject(model);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getProject()).thenReturn(project);

        final FileSet fs = new FileSet();

        final ModuleSources sources = new ModuleSources();
        sources.setIncludeModuleDirectory(true);

        final File basedir = temporaryFolder.getRoot();

        final MavenProject artifactProject = new MavenProject(new Model());
        artifactProject.setGroupId("GROUPID");
        artifactProject.setFile(new File(basedir, "pom.xml"));

        Artifact artifact = mock(Artifact.class);
        when(artifact.getGroupId()).thenReturn("GROUPID");
        when(artifact.getArtifactId()).thenReturn("artifact");

        artifactProject.setArtifact(artifact);

        DefaultAssemblyArchiverTest.setupInterpolators(configSource, project);

        final FileSet result = this.phase.createFileSet(fs, sources, artifactProject, configSource);

        assertEquals("artifact/", result.getOutputDirectory());

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource, atLeastOnce()).getFinalName();
        verify(configSource, atLeastOnce()).getMavenSession();
        verify(configSource, atLeastOnce()).getProject();
    }

    @Test
    public void testCreateFileSet_ShouldPrependModuleDirWhenOutDirIsProvided() throws Exception {
        final Model model = new Model();
        model.setArtifactId("artifact");

        final MavenProject project = new MavenProject(model);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getProject()).thenReturn(project);

        final FileSet fs = new FileSet();
        fs.setOutputDirectory("out");

        final ModuleSources sources = new ModuleSources();
        sources.setIncludeModuleDirectory(true);

        final MavenProject artifactProject = new MavenProject(new Model());
        artifactProject.setGroupId("GROUPID");

        final File basedir = temporaryFolder.getRoot();

        artifactProject.setFile(new File(basedir, "pom.xml"));

        Artifact artifact = mock(Artifact.class);
        when(artifact.getGroupId()).thenReturn("GROUPID");
        when(artifact.getArtifactId()).thenReturn("artifact");

        artifactProject.setArtifact(artifact);
        DefaultAssemblyArchiverTest.setupInterpolators(configSource, project /* or artifactProject */);

        final FileSet result = this.phase.createFileSet(fs, sources, artifactProject, configSource);

        assertEquals("artifact/out/", result.getOutputDirectory());

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource, atLeastOnce()).getFinalName();
        verify(configSource, atLeastOnce()).getMavenSession();
        verify(configSource, atLeastOnce()).getProject();
    }

    @Test
    public void testCreateFileSet_ShouldAddExcludesForSubModulesWhenExcludeSubModDirsIsTrue() throws Exception {
        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);

        final FileSet fs = new FileSet();

        final ModuleSources sources = new ModuleSources();
        sources.setExcludeSubModuleDirectories(true);

        final Model model = new Model();
        model.setArtifactId("artifact");

        model.addModule("submodule");

        final MavenProject project = new MavenProject(model);

        final File basedir = temporaryFolder.getRoot();
        project.setGroupId("GROUPID");
        project.setFile(new File(basedir, "pom.xml"));

        Artifact artifact = mock(Artifact.class);
        when(artifact.getGroupId()).thenReturn("GROUPID");

        project.setArtifact(artifact);
        DefaultAssemblyArchiverTest.setupInterpolators(configSource, project);

        final FileSet result = this.phase.createFileSet(fs, sources, project, configSource);

        assertEquals(1, result.getExcludes().size());
        assertEquals("submodule/**", result.getExcludes().get(0));

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource, atLeastOnce()).getFinalName();
        verify(configSource, atLeastOnce()).getMavenSession();
        verify(configSource, atLeastOnce()).getProject();
    }

    @Test
    public void testExecute_ShouldSkipIfNoModuleSetsFound() throws Exception {
        final Assembly assembly = new Assembly();
        assembly.setIncludeBaseDirectory(false);

        this.phase.execute(assembly, null, null);
    }

    @Test
    public void testExecute_ShouldAddOneModuleSetWithOneModuleInIt() throws Exception {
        final MavenProject project = createProject("group", "artifact", "version", null);

        final MavenProject module = createProject("group", "module", "version", project);

        Artifact artifact = mock(Artifact.class);
        final File moduleArtifactFile = temporaryFolder.newFile();
        when(artifact.getGroupId()).thenReturn("GROUPID");
        when(artifact.getFile()).thenReturn(moduleArtifactFile);
        module.setArtifact(artifact);

        final List<MavenProject> projects = new ArrayList<>();
        projects.add(module);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getReactorProjects()).thenReturn(projects);
        when(configSource.getFinalName()).thenReturn("final-name");
        when(configSource.getProject()).thenReturn(project);

        final Archiver archiver = mock(Archiver.class);
        when(archiver.getDestFile()).thenReturn(new File("junk"));
        when(archiver.getOverrideDirectoryMode()).thenReturn(0777);
        when(archiver.getOverrideFileMode()).thenReturn(0777);

        final ModuleBinaries bin = new ModuleBinaries();
        bin.setOutputFileNameMapping("artifact");
        bin.setOutputDirectory("out");
        bin.setFileMode("777");
        bin.setUnpack(false);
        bin.setIncludeDependencies(false);

        final ModuleSet ms = new ModuleSet();
        ms.setBinaries(bin);

        final Assembly assembly = new Assembly();
        assembly.setIncludeBaseDirectory(false);
        assembly.addModuleSet(ms);

        when(dependencyResolver.resolveDependencySets(eq(assembly), eq(ms), eq(configSource), anyList()))
                .thenReturn(new LinkedHashMap<>());
        DefaultAssemblyArchiverTest.setupInterpolators(configSource, module);

        this.phase.execute(assembly, archiver, configSource);

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource, atLeastOnce()).getFinalName();
        verify(configSource, atLeastOnce()).getMavenSession();
        verify(configSource, atLeastOnce()).getProject();
        verify(configSource, atLeastOnce()).getReactorProjects();

        verify(archiver).addFile(moduleArtifactFile, "out/artifact", 511);
        verify(archiver, atLeastOnce()).getDestFile();
        verify(archiver).getOverrideDirectoryMode();
        verify(archiver).getOverrideFileMode();
        verify(archiver, times(2)).setFileMode(511);

        verify(dependencyResolver).resolveDependencySets(eq(assembly), eq(ms), eq(configSource), anyList());
    }

    @Test
    public void testAddModuleBinaries_ShouldReturnImmediatelyWhenBinariesIsNull() throws Exception {
        this.phase.addModuleBinaries(null, null, null, null, null, null);
    }

    @Test
    public void testAddModuleBinaries_ShouldFilterPomModule() throws Exception {
        final ModuleBinaries binaries = new ModuleBinaries();

        binaries.setUnpack(false);
        binaries.setFileMode("777");
        binaries.setOutputDirectory("out");
        binaries.setOutputFileNameMapping("artifact");

        final MavenProject project = createProject("group", "artifact", "version", null);
        project.setPackaging("pom");

        Artifact artifact = mock(Artifact.class);
        project.setArtifact(artifact);

        final Set<MavenProject> projects = singleton(project);

        this.phase.addModuleBinaries(null, null, binaries, projects, null, null);
    }

    @Test
    public void testAddModuleBinaries_ShouldAddOneModuleAttachmentArtifactAndNoDeps() throws Exception {
        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getFinalName()).thenReturn("final-name");

        Artifact artifact = mock(Artifact.class);
        when(artifact.getGroupId()).thenReturn("GROUPID");
        when(artifact.getClassifier()).thenReturn("test");
        final File artifactFile = temporaryFolder.newFile();
        when(artifact.getFile()).thenReturn(artifactFile);

        final Archiver archiver = mock(Archiver.class);
        when(archiver.getDestFile()).thenReturn(new File("junk"));
        when(archiver.getOverrideDirectoryMode()).thenReturn(0222);
        when(archiver.getOverrideFileMode()).thenReturn(0222);

        final ModuleBinaries binaries = new ModuleBinaries();

        binaries.setIncludeDependencies(false);
        binaries.setUnpack(false);
        binaries.setFileMode("777");
        binaries.setOutputDirectory("out");
        binaries.setOutputFileNameMapping("artifact");
        binaries.setAttachmentClassifier("test");

        final MavenProject project = createProject("group", "artifact", "version", null);
        project.addAttachedArtifact(artifact);

        final Set<MavenProject> projects = singleton(project);

        when(dependencyResolver.resolveDependencySets(isNull(), isNull(), eq(configSource), anyList()))
                .thenReturn(new LinkedHashMap<>());
        DefaultAssemblyArchiverTest.setupInterpolators(configSource, project);

        this.phase.addModuleBinaries(null, null, binaries, projects, archiver, configSource);

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource, atLeastOnce()).getFinalName();
        verify(configSource, atLeastOnce()).getMavenSession();
        verify(configSource, atLeastOnce()).getProject();

        verify(archiver).addFile(artifactFile, "out/artifact", 511);
        verify(archiver, atLeastOnce()).getDestFile();
        verify(archiver).getOverrideDirectoryMode();
        verify(archiver).getOverrideFileMode();
        verify(archiver).setFileMode(511);
        verify(archiver).setFileMode(146);

        verify(dependencyResolver).resolveDependencySets(isNull(), isNull(), eq(configSource), anyList());
    }

    @Test
    public void testAddModuleBinaries_ShouldFailWhenOneModuleDoesntHaveAttachmentWithMatchingClassifier()
            throws Exception {
        Artifact artifact = mock(Artifact.class);

        final ModuleBinaries binaries = new ModuleBinaries();

        binaries.setUnpack(false);
        binaries.setFileMode("777");
        binaries.setOutputDirectory("out");
        binaries.setOutputFileNameMapping("artifact");
        binaries.setAttachmentClassifier("test");

        final MavenProject project = createProject("group", "artifact", "version", null);
        project.setArtifact(artifact);

        final Set<MavenProject> projects = singleton(project);

        try {

            this.phase.addModuleBinaries(null, null, binaries, projects, null, null);

            fail("Should throw an invalid configuration exception because of module with missing attachment.");
        } catch (final InvalidAssemblerConfigurationException e) {
            assertEquals(
                    "Cannot find attachment with classifier: test in module project: group:artifact:jar:version. "
                            + "Please exclude this module from the module-set.",
                    e.getMessage());
            // should throw this because of missing attachment.
        }
    }

    @Test
    public void testAddModuleBinaries_ShouldAddOneModuleArtifactAndNoDeps() throws Exception {
        Artifact artifact = mock(Artifact.class);
        final File artifactFile = temporaryFolder.newFile();
        when(artifact.getGroupId()).thenReturn("GROUPID");
        when(artifact.getFile()).thenReturn(artifactFile);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getFinalName()).thenReturn("final-name");

        final Archiver archiver = mock(Archiver.class);
        when(archiver.getDestFile()).thenReturn(new File("junk"));
        when(archiver.getOverrideDirectoryMode()).thenReturn(0222);
        when(archiver.getOverrideFileMode()).thenReturn(0222);

        final ModuleBinaries binaries = new ModuleBinaries();

        binaries.setIncludeDependencies(false);
        binaries.setUnpack(false);
        binaries.setFileMode("777");
        binaries.setOutputDirectory("out");
        binaries.setOutputFileNameMapping("artifact");

        final MavenProject project = createProject("group", "artifact", "version", null);
        project.setArtifact(artifact);

        final Set<MavenProject> projects = singleton(project);

        when(dependencyResolver.resolveDependencySets(
                        isNull(), isNull(), any(AssemblerConfigurationSource.class), anyList()))
                .thenReturn(new LinkedHashMap<>());
        DefaultAssemblyArchiverTest.setupInterpolators(configSource, project);

        this.phase.addModuleBinaries(null, null, binaries, projects, archiver, configSource);

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource, atLeastOnce()).getFinalName();
        verify(configSource, atLeastOnce()).getMavenSession();
        verify(configSource, atLeastOnce()).getProject();

        verify(dependencyResolver)
                .resolveDependencySets(isNull(), isNull(), any(AssemblerConfigurationSource.class), anyList());

        verify(archiver).addFile(artifactFile, "out/artifact", 511);
        verify(archiver, atLeastOnce()).getDestFile();
        verify(archiver).getOverrideDirectoryMode();
        verify(archiver).getOverrideFileMode();
        verify(archiver).setFileMode(511);
        verify(archiver).setFileMode(146);
    }

    @Test
    public void testAddModuleArtifact_ShouldThrowExceptionWhenArtifactFileIsNull() throws Exception {
        Artifact artifact = mock(Artifact.class);
        try {
            this.phase.addModuleArtifact(artifact, null, null, null, null);

            fail("Expected ArchiveCreationException since artifact file is null.");
        } catch (final ArchiveCreationException e) {
            // expected
        }
    }

    @Test
    public void testAddModuleArtifact_ShouldAddOneArtifact() throws Exception {
        Artifact artifact = mock(Artifact.class);
        when(artifact.getGroupId()).thenReturn("GROUPID");
        final File artifactFile = temporaryFolder.newFile();
        when(artifact.getFile()).thenReturn(artifactFile);

        final MavenProject project = createProject("group", "artifact", "version", null);
        project.setArtifact(artifact);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getFinalName()).thenReturn("final-name");

        final Archiver archiver = mock(Archiver.class);
        when(archiver.getDestFile()).thenReturn(new File("junk"));
        when(archiver.getOverrideDirectoryMode()).thenReturn(0222);
        when(archiver.getOverrideFileMode()).thenReturn(0222);

        final ModuleBinaries binaries = new ModuleBinaries();
        binaries.setOutputDirectory("out");
        binaries.setOutputFileNameMapping("artifact");
        binaries.setUnpack(false);
        binaries.setFileMode("777");
        DefaultAssemblyArchiverTest.setupInterpolators(configSource, project);

        this.phase.addModuleArtifact(artifact, project, archiver, configSource, binaries);

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource, atLeastOnce()).getFinalName();
        verify(configSource, atLeastOnce()).getMavenSession();
        verify(configSource, atLeastOnce()).getProject();

        verify(archiver).addFile(artifactFile, "out/artifact", 511);
        verify(archiver, atLeastOnce()).getDestFile();
        verify(archiver).getOverrideDirectoryMode();
        verify(archiver).getOverrideFileMode();
        verify(archiver).setFileMode(511);
        verify(archiver).setFileMode(146);
    }

    @Test
    public void testAddModuleSourceFileSets_ShouldReturnImmediatelyIfSourcesIsNull() throws Exception {
        this.phase.addModuleSourceFileSets(null, null, null, null);
    }

    @Test
    public void testAddModuleSourceFileSets_ShouldAddOneSourceDirectory() throws Exception {
        final MavenProject project = createProject("group", "artifact", "version", null);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getFinalName()).thenReturn("final-name");
        when(configSource.getProject()).thenReturn(project);
        Artifact artifact = mock(Artifact.class);
        when(artifact.getGroupId()).thenReturn("GROUPID");
        project.setArtifact(artifact);

        final Set<MavenProject> projects = singleton(project);

        final FileSet fs = new FileSet();
        fs.setDirectory("/src");
        fs.setDirectoryMode("777");
        fs.setFileMode("777");

        final ModuleSources sources = new ModuleSources();
        sources.addFileSet(fs);

        // the logger sends a debug message with this info inside the addFileSet(..) method..
        final Archiver archiver = mock(Archiver.class);
        when(archiver.getOverrideDirectoryMode()).thenReturn(-1);
        when(archiver.getOverrideFileMode()).thenReturn(-1);

        DefaultAssemblyArchiverTest.setupInterpolators(configSource, project);

        this.phase.addModuleSourceFileSets(sources, projects, archiver, configSource);

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource).getArchiveBaseDirectory();
        verify(configSource, atLeastOnce()).getFinalName();
        verify(configSource, atLeastOnce()).getProject();
        verify(configSource, atLeastOnce()).getMavenSession();

        verify(archiver).getOverrideDirectoryMode();
        verify(archiver).getOverrideFileMode();
    }

    @Test
    public void testGetModuleProjects_ShouldReturnNothingWhenReactorContainsOnlyCurrentProject() throws Exception {
        final MavenProject project = createProject("group", "artifact", "version", null);

        final List<MavenProject> projects = Collections.singletonList(project);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getProject()).thenReturn(project);
        when(configSource.getReactorProjects()).thenReturn(projects);

        final ModuleSet moduleSet = new ModuleSet();
        moduleSet.setIncludeSubModules(true);

        final Set<MavenProject> moduleProjects =
                ModuleSetAssemblyPhase.getModuleProjects(moduleSet, configSource, logger);

        assertTrue(moduleProjects.isEmpty());

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource).getReactorProjects();
        verify(configSource, atLeastOnce()).getProject();
    }

    @Test
    public void testGetModuleProjects_ShouldReturnNothingWhenReactorContainsTwoSiblingProjects() throws Exception {
        final MavenProject project = createProject("group", "artifact", "version", null);
        final MavenProject project2 = createProject("group", "artifact2", "version", null);

        final List<MavenProject> projects = new ArrayList<>();
        projects.add(project);
        projects.add(project2);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getReactorProjects()).thenReturn(projects);
        when(configSource.getProject()).thenReturn(project);

        final ModuleSet moduleSet = new ModuleSet();
        moduleSet.setIncludeSubModules(true);

        final Set<MavenProject> moduleProjects =
                ModuleSetAssemblyPhase.getModuleProjects(moduleSet, configSource, logger);

        assertTrue(moduleProjects.isEmpty());

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource).getReactorProjects();
        verify(configSource, atLeastOnce()).getProject();
    }

    @Test
    public void testGetModuleProjects_ShouldReturnModuleOfCurrentProject() throws Exception {
        final MavenProject project = createProject("group", "artifact", "version", null);
        final MavenProject project2 = createProject("group", "artifact2", "version", project);

        final List<MavenProject> projects = new ArrayList<>();
        projects.add(project);
        projects.add(project2);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getReactorProjects()).thenReturn(projects);
        when(configSource.getProject()).thenReturn(project);

        final ModuleSet moduleSet = new ModuleSet();
        moduleSet.setIncludeSubModules(true);

        final Set<MavenProject> moduleProjects =
                ModuleSetAssemblyPhase.getModuleProjects(moduleSet, configSource, logger);

        assertFalse(moduleProjects.isEmpty());

        final MavenProject result = moduleProjects.iterator().next();

        assertEquals("artifact2", result.getArtifactId());

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource).getReactorProjects();
        verify(configSource, atLeastOnce()).getProject();
    }

    @Test
    public void testGetModuleProjects_ShouldReturnDescendentModulesOfCurrentProject() throws Exception {
        final MavenProject project = createProject("group", "artifact", "version", null);
        final MavenProject project2 = createProject("group", "artifact2", "version", project);
        final MavenProject project3 = createProject("group", "artifact3", "version", project2);

        final List<MavenProject> projects = new ArrayList<>();
        projects.add(project);
        projects.add(project2);
        projects.add(project3);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getReactorProjects()).thenReturn(projects);
        when(configSource.getProject()).thenReturn(project);

        final ModuleSet moduleSet = new ModuleSet();
        moduleSet.setIncludeSubModules(true);

        final Set<MavenProject> moduleProjects =
                ModuleSetAssemblyPhase.getModuleProjects(moduleSet, configSource, logger);

        assertEquals(2, moduleProjects.size());

        final List<MavenProject> check = new ArrayList<>();
        check.add(project2);
        check.add(project3);

        verifyResultIs(check, moduleProjects);

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource).getReactorProjects();
        verify(configSource, atLeastOnce()).getProject();
    }

    @Test
    public void testGetModuleProjects_ShouldExcludeModuleAndDescendentsTransitively() throws Exception {
        final MavenProject project = createProject("group", "artifact", "version", null);

        Artifact artifact1 = mock(Artifact.class);
        project.setArtifact(artifact1);

        final MavenProject project2 = createProject("group", "artifact2", "version", project);
        Artifact artifact2 = mock(Artifact.class);
        when(artifact2.getGroupId()).thenReturn("group");
        when(artifact2.getArtifactId()).thenReturn("artifact2");
        lenient().when(artifact2.getId()).thenReturn("group:artifact2:version:jar");
        lenient().when(artifact2.getDependencyConflictId()).thenReturn("group:artifact2:jar");
        project2.setArtifact(artifact2);

        final MavenProject project3 = createProject("group", "artifact3", "version", project2);
        Artifact artifact3 = mock(Artifact.class);
        when(artifact3.getGroupId()).thenReturn("group");
        when(artifact3.getArtifactId()).thenReturn("artifact3");
        lenient().when(artifact3.getId()).thenReturn("group:artifact3:version:jar");
        lenient().when(artifact3.getDependencyConflictId()).thenReturn("group:artifact3:jar");
        when(artifact3.getDependencyTrail()).thenReturn(Arrays.asList(project2.getId(), project.getId()));
        project3.setArtifact(artifact3);

        final List<MavenProject> projects = new ArrayList<>();
        projects.add(project);
        projects.add(project2);
        projects.add(project3);

        final AssemblerConfigurationSource configSource = mock(AssemblerConfigurationSource.class);
        when(configSource.getReactorProjects()).thenReturn(projects);
        when(configSource.getProject()).thenReturn(project);

        final ModuleSet moduleSet = new ModuleSet();
        moduleSet.setIncludeSubModules(true);

        moduleSet.addExclude("group:artifact2");

        final Set<MavenProject> moduleProjects =
                ModuleSetAssemblyPhase.getModuleProjects(moduleSet, configSource, logger);

        assertTrue(moduleProjects.isEmpty());

        // result of easymock migration, should be assert of expected result instead of verifying methodcalls
        verify(configSource).getReactorProjects();
        verify(configSource, atLeastOnce()).getProject();
    }

    private void verifyResultIs(final List<MavenProject> check, final Set<MavenProject> moduleProjects) {
        boolean failed = false;

        final Set<MavenProject> checkTooMany = new HashSet<>(moduleProjects);
        check.forEach(checkTooMany::remove);

        if (!checkTooMany.isEmpty()) {
            failed = true;

            System.out.println("Unexpected projects in output: ");

            for (final MavenProject project : checkTooMany) {
                System.out.println(project.getId());
            }
        }

        final Set<MavenProject> checkTooFew = new HashSet<>(check);
        checkTooFew.removeAll(moduleProjects);

        if (!checkTooFew.isEmpty()) {
            failed = true;

            System.out.println("Expected projects missing from output: ");

            for (final MavenProject project : checkTooMany) {
                System.out.println(project.getId());
            }
        }

        if (failed) {
            fail("See system output for more information.");
        }
    }

    private MavenProject createProject(
            final String groupId, final String artifactId, final String version, final MavenProject parentProject) {
        final Model model = new Model();
        model.setArtifactId(artifactId);
        model.setGroupId(groupId);
        model.setVersion(version);

        final MavenProject project = new MavenProject(model);

        File pomFile;
        if (parentProject == null) {
            final File basedir = temporaryFolder.getRoot();
            pomFile = new File(basedir, "pom.xml");
        } else {
            final File parentBase = parentProject.getBasedir();
            pomFile = new File(parentBase, artifactId + "/pom.xml");

            parentProject.getModel().addModule(artifactId);
            project.setParent(parentProject);
        }

        project.setFile(pomFile);

        return project;
    }
}
