package com.vackosar.gitflowincrementalbuild.boundary;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.project.MavenProject;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import com.google.common.collect.ImmutableMap;
import com.vackosar.gitflowincrementalbuild.control.Property;

/**
 * Tests {@link UnchangedProjectsRemover} with Mockito mocks.
 *
 * @author famod
 */
public class UnchangedProjectsRemoverTest extends BaseUnchangedProjectsRemoverTest {

    private static final String AID_MODULE_B_DEP_WAR = AID_MODULE_B + "-dependent-war";

    @Test
    public void nothingChanged() throws GitAPIException, IOException {
        MavenProject moduleB = addModuleMock(AID_MODULE_B, false);

        underTest.act();

        assertEquals(Collections.singletonList("validate"), mavenSessionMock.getGoals(), "Unexpected goal");

        verify(mavenSessionMock).setProjects(Collections.singletonList(moduleA));

        assertProjectPropertiesEqual(moduleA, Collections.emptyMap());
        assertProjectPropertiesEqual(moduleB, Collections.emptyMap());
    }

    @Test
    public void nothingChanged_buildAllIfNoChanges() throws GitAPIException, IOException {
        MavenProject unchangedModuleMock = addModuleMock(AID_MODULE_B, false);

        addGibProperty(Property.buildAllIfNoChanges, "true");
        addGibProperty(Property.skipTestsForUpstreamModules, "true");

        underTest.act();

        assertEquals(Collections.emptyList(), mavenSessionMock.getGoals(), "Unexpected goals");

        verify(mavenSessionMock, never()).setProjects(anyList());

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("maven.test.skip", "true"));
        assertProjectPropertiesEqual(unchangedModuleMock, ImmutableMap.of("maven.test.skip", "true"));
    }

    // mvn -f ... for a multi-module-submodule
    @Test
    public void nothingChanged_singleModule_withSubmodules() throws GitAPIException, IOException {
        // note: a more realistic setup would require a proper parent/root
        moduleA.getModel().addModule("test");

        underTest.act();

        assertEquals(Collections.singletonList("validate"), mavenSessionMock.getGoals(), "Unexpected goal");

        verify(mavenSessionMock).setProjects(Collections.singletonList(moduleA));
        verify(moduleA, Mockito.times(2)).getModel();   // +1 due to test setup

        assertProjectPropertiesEqual(moduleA, Collections.emptyMap());
    }

    // mvn -N ... for a multi-module-submodule
    @Test
    public void nothingChanged_singleModule_withSubmodules_nonRecursive() throws GitAPIException, IOException {
        // note: a more realistic setup would require a proper parent/root
        moduleA.getModel().addModule("test");
        when(mavenExecutionRequestMock.isRecursive()).thenReturn(false);

        underTest.act();

        assertEquals(Collections.emptyList(), mavenSessionMock.getGoals(), "Unexpected goals");

        verify(mavenSessionMock, never()).setProjects(anyList());
        verify(moduleA).getModel();   // only once due to test setup

        assertProjectPropertiesEqual(moduleA, Collections.emptyMap());
    }

    // mvn -f module-B (or unusal case of a non-multi-module project)
    @Test
    public void nothingChanged_singleModule_leaf() throws GitAPIException, IOException {
        MavenProject moduleB = addModuleMock(AID_MODULE_B, false);

        // emulate -f module-B
        overrideProjects(moduleB);

        underTest.act();

        assertEquals(Collections.emptyList(), mavenSessionMock.getGoals(), "Unexpected goals");

        verify(mavenSessionMock, never()).setProjects(anyList());
        verify(moduleB).getModel();

        assertProjectPropertiesEqual(moduleA, Collections.emptyMap());
        assertProjectPropertiesEqual(moduleB, Collections.emptyMap());
    }

    @Test
    public void singleChanged() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);

        underTest.act();

        verify(mavenSessionMock).setProjects(Collections.singletonList(changedModuleMock));
    }

    @Test
    public void singleChanged_buildUpstream() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock));

        assertProjectPropertiesEqual(moduleA, Collections.emptyMap());
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildUpstream_skipTestsForUpstreamModules() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        addGibProperty(Property.skipTestsForUpstreamModules, "true");

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock));

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("maven.test.skip", "true"));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildUpstream_skipTestsForUpstreamModules_jarGoal() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        addGibProperty(Property.skipTestsForUpstreamModules, "true");

        Plugin pluginMock = mock(Plugin.class);
        PluginExecution execMock = mock(PluginExecution.class);
        when(execMock.getGoals()).thenReturn(Collections.singletonList("test-jar"));
        when(pluginMock.getExecutions()).thenReturn(Collections.singletonList(execMock));
        when(moduleA.getBuildPlugins()).thenReturn(Collections.singletonList(pluginMock));

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock));

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("skipTests", "true"));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildUpstream_argsForUpstreamModules() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        addGibProperty(Property.argsForUpstreamModules, "enforcer.skip=true argWithNoValue");

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock));

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("enforcer.skip", "true", "argWithNoValue", ""));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildUpstream_modeChanged() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);
        MavenProject dependsOnBothModuleMock = addModuleMock("changed-and-unchanged-dependent", false);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        setUpAndDownstreamsForBuildUpstreamModeTests(changedModuleMock, unchangedModuleMock, dependsOnBothModuleMock);

        addGibProperty(Property.buildUpstreamMode, "changed");

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock, dependsOnBothModuleMock));

        assertProjectPropertiesEqual(moduleA, Collections.emptyMap());
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(unchangedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(dependsOnBothModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildUpstream_modeChanged_argsForUpstreamModules() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);
        MavenProject dependsOnBothModuleMock = addModuleMock("changed-and-unchanged-dependent", false);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        setUpAndDownstreamsForBuildUpstreamModeTests(changedModuleMock, unchangedModuleMock, dependsOnBothModuleMock);

        addGibProperty(Property.buildUpstreamMode, "changed");
        addGibProperty(Property.argsForUpstreamModules, "foo=bar");

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock, dependsOnBothModuleMock));

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("foo", "bar"));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(unchangedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(dependsOnBothModuleMock, Collections.emptyMap());
    }

    // linear: A <- B <- C <- D
    @Test
    public void singleChanged_buildUpstream_modeChanged_argsForUpstreamModules_linear() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedIntermediateModuleMock = addModuleMock("module-C", false);
        MavenProject dependsOnIntermediateModuleMock = addModuleMock("module-D", true);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        setUpstreamProjects(dependsOnIntermediateModuleMock, unchangedIntermediateModuleMock, changedModuleMock, moduleA);
        setDownstreamProjects(changedModuleMock, unchangedIntermediateModuleMock, dependsOnIntermediateModuleMock);
        setDownstreamProjects(unchangedIntermediateModuleMock, dependsOnIntermediateModuleMock);

        addGibProperty(Property.buildUpstreamMode, "changed");
        addGibProperty(Property.argsForUpstreamModules, "foo=bar");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(moduleA, changedModuleMock, unchangedIntermediateModuleMock, dependsOnIntermediateModuleMock));

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("foo", "bar"));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(unchangedIntermediateModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(dependsOnIntermediateModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildUpstream_modeImpacted() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);
        MavenProject dependsOnBothModuleMock = addModuleMock("changed-and-unchanged-dependent", false);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        setUpAndDownstreamsForBuildUpstreamModeTests(changedModuleMock, unchangedModuleMock, dependsOnBothModuleMock);

        addGibProperty(Property.buildUpstreamMode, "impacted");

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock, unchangedModuleMock, dependsOnBothModuleMock));

        assertProjectPropertiesEqual(moduleA, Collections.emptyMap());
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(unchangedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(dependsOnBothModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildUpstream_modeImpacted_argsForUpstreamModules() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);
        MavenProject dependsOnBothModuleMock = addModuleMock("changed-and-unchanged-dependent", false);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        setUpAndDownstreamsForBuildUpstreamModeTests(changedModuleMock, unchangedModuleMock, dependsOnBothModuleMock);

        addGibProperty(Property.buildUpstreamMode, "impacted");
        addGibProperty(Property.argsForUpstreamModules, "foo=bar");

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock, unchangedModuleMock, dependsOnBothModuleMock));

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("foo", "bar"));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(unchangedModuleMock, ImmutableMap.of("foo", "bar"));
        assertProjectPropertiesEqual(dependsOnBothModuleMock, Collections.emptyMap());
    }

    // linear: A <- B <- C <- D
    @Test
    public void singleChanged_buildUpstream_modeImpacted_argsForUpstreamModules_linear() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedIntermediateModuleMock = addModuleMock("module-C", false);
        MavenProject dependsOnIntermediateModuleMock = addModuleMock("module-D", false);

        when(mavenExecutionRequestMock.getMakeBehavior()).thenReturn(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);

        setUpstreamProjects(dependsOnIntermediateModuleMock, unchangedIntermediateModuleMock, changedModuleMock, moduleA);
        setDownstreamProjects(changedModuleMock, unchangedIntermediateModuleMock, dependsOnIntermediateModuleMock);
        setDownstreamProjects(unchangedIntermediateModuleMock, dependsOnIntermediateModuleMock);

        addGibProperty(Property.buildUpstreamMode, "impacted");
        addGibProperty(Property.argsForUpstreamModules, "foo=bar");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(moduleA, changedModuleMock, unchangedIntermediateModuleMock, dependsOnIntermediateModuleMock));

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("foo", "bar"));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(unchangedIntermediateModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(dependsOnIntermediateModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildAll_argsForUpstreamModules() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);

        addGibProperty(Property.argsForUpstreamModules, "enforcer.skip=true argWithNoValue");
        addGibProperty(Property.buildAll, "true");

        underTest.act();

        verify(mavenSessionMock, never()).setProjects(anyList());

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("enforcer.skip", "true", "argWithNoValue", ""));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_buildDownstream_enabled() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentModuleMock = addModuleMock(AID_MODULE_B + "-dependent-jar", false);

        setUpstreamProjects(dependentModuleMock, changedModuleMock, moduleA);
        setDownstreamProjects(changedModuleMock, dependentModuleMock);

        // buildDownstream is enabled by default!

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(changedModuleMock, dependentModuleMock));
    }

    @Test
    public void singleChanged_buildDownstream_disabled() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentModuleMock = addModuleMock(AID_MODULE_B + "-dependent-jar", false);

        setUpstreamProjects(dependentModuleMock, changedModuleMock, moduleA);
        setDownstreamProjects(changedModuleMock, dependentModuleMock);

        addGibProperty(Property.buildDownstream, "false");

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(changedModuleMock));
    }

    @Test
    public void singleChanged_buildDownstream_disabled_buildAll_argsForUpstreamModules() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentModuleMock = addModuleMock(AID_MODULE_B + "-dependent-jar", false);

        setUpstreamProjects(dependentModuleMock, changedModuleMock, moduleA);
        setDownstreamProjects(changedModuleMock, dependentModuleMock);

        addGibProperty(Property.buildDownstream, "false");
        addGibProperty(Property.buildAll, "true");
        addGibProperty(Property.argsForUpstreamModules, "foo=bar");

        underTest.act();

        verify(mavenSessionMock, never()).setProjects(anyList());

        assertProjectPropertiesEqual(moduleA, ImmutableMap.of("foo", "bar"));
        assertProjectPropertiesEqual(changedModuleMock, Collections.emptyMap());
        assertProjectPropertiesEqual(dependentModuleMock, Collections.emptyMap());
    }

    @Test
    public void singleChanged_forceBuildModules() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);

        addGibProperty(Property.forceBuildModules, moduleA.getArtifactId());

        underTest.act();

        verify(mavenSessionMock).setProjects(Arrays.asList(moduleA, changedModuleMock));
    }

    @Test
    public void singleChanged_forceBuildModules_two() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedModuleMock = addModuleMock("unchanged-module", false);

        addGibProperty(Property.forceBuildModules, moduleA.getArtifactId() + "," + unchangedModuleMock.getArtifactId());

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(moduleA, changedModuleMock, unchangedModuleMock));
    }

    @Test
    public void singleChanged_forceBuildModules_oneWildcard() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedModuleMock = addModuleMock(AID_MODULE_A + "-2", false);

        addGibProperty(Property.forceBuildModules, AID_MODULE_A + ".*");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(moduleA, changedModuleMock, unchangedModuleMock));
    }

    @Test
    public void singleChanged_forceBuildModules_twoWildcards() throws GitAPIException, IOException {
        MavenProject changedModuleMock = addModuleMock(AID_MODULE_B, true);
        MavenProject unchangedModuleMock = addModuleMock("module-C", false);

        addGibProperty(Property.forceBuildModules, AID_MODULE_A +  ".*,.*-C");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(moduleA, changedModuleMock, unchangedModuleMock));
    }

    @Test
    public void singleChanged_excludeDownstreamModulesPackagedAs_oneTransitive_oneExcluded() throws GitAPIException, IOException {
        MavenProject changedProjectMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentWar = addModuleMock(AID_MODULE_B_DEP_WAR, false, "war");

        setDownstreamProjects(changedProjectMock, dependentWar);

        addGibProperty(Property.excludeDownstreamModulesPackagedAs, "war");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(changedProjectMock));
    }

    @Test
    public void singleChanged_excludeDownstreamModulesPackagedAs_twoTransitive_oneExcluded() throws GitAPIException, IOException {
        MavenProject changedProjectMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentWar = addModuleMock(AID_MODULE_B_DEP_WAR, false, "war");
        MavenProject dependentJar = addModuleMock(AID_MODULE_B + "-dependent-jar", false);

        setDownstreamProjects(changedProjectMock, dependentWar, dependentJar);

        addGibProperty(Property.excludeDownstreamModulesPackagedAs, "war");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(changedProjectMock, dependentJar));
    }

    @Test
    public void singleChanged_excludeDownstreamModulesPackagedAs_twoTransitive_twoExcluded() throws GitAPIException, IOException {
        MavenProject changedProjectMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentWar = addModuleMock(AID_MODULE_B_DEP_WAR, false, "war");
        MavenProject dependentEar = addModuleMock(AID_MODULE_B + "-dependent-ear", false, "ear");

        setDownstreamProjects(changedProjectMock, dependentWar, dependentEar);

        addGibProperty(Property.excludeDownstreamModulesPackagedAs, "war,ear");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(changedProjectMock));
    }

    @Test
    public void singleChanged_excludeDownstreamModulesPackagedAs_oneTransitive_buildAll() throws GitAPIException, IOException {
        MavenProject changedProjectMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentWar = addModuleMock(AID_MODULE_B_DEP_WAR, false, "war");

        setDownstreamProjects(changedProjectMock, dependentWar);

        addGibProperty(Property.excludeDownstreamModulesPackagedAs, "war");
        addGibProperty(Property.buildAll, "true");

        underTest.act();

        verify(mavenSessionMock, never())
                .setProjects(anyList());
    }

    @Test
    public void singleChanged_excludeDownstreamModulesPackagedAs_oneTransitive_forceBuildModules() throws GitAPIException, IOException {
        MavenProject changedProjectMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentWar = addModuleMock(AID_MODULE_B_DEP_WAR, false, "war");

        setDownstreamProjects(changedProjectMock, dependentWar);

        addGibProperty(Property.excludeDownstreamModulesPackagedAs, "war");
        addGibProperty(Property.forceBuildModules, dependentWar.getArtifactId());

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(changedProjectMock, dependentWar));
    }

    @Test
    public void twoChanged_excludeDownstreamModulesPackagedAs_changedNotExcluded() throws GitAPIException, IOException {
        MavenProject changedProjectMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentWar = addModuleMock(AID_MODULE_B_DEP_WAR, true, "war");

        // war module is changed, must be retained!

        addGibProperty(Property.excludeDownstreamModulesPackagedAs, "war");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(changedProjectMock, dependentWar));
    }

    @Test
    public void twoChanged_excludeDownstreamModulesPackagedAs_changedNotExcluded_transitive() throws GitAPIException, IOException {
        MavenProject changedProjectMock = addModuleMock(AID_MODULE_B, true);
        MavenProject dependentWar = addModuleMock(AID_MODULE_B_DEP_WAR, true, "war");

        // war module is changed, must be retained - even if depending on changedProjectMock!
        setDownstreamProjects(changedProjectMock, dependentWar);

        addGibProperty(Property.excludeDownstreamModulesPackagedAs, "war");

        underTest.act();

        verify(mavenSessionMock).setProjects(
                Arrays.asList(changedProjectMock, dependentWar));
    }

    private void setUpAndDownstreamsForBuildUpstreamModeTests(MavenProject changedModuleMock, MavenProject unchangedModuleMock,
            MavenProject dependsOnBothModuleMock) {
        // dependsOnBothModuleMock directly depends on both changedModuleMock & unchangedModuleMock + transitively on moduleA
        setUpstreamProjects(dependsOnBothModuleMock, changedModuleMock, unchangedModuleMock, moduleA);
        setDownstreamProjects(changedModuleMock, dependsOnBothModuleMock);
        setDownstreamProjects(unchangedModuleMock, dependsOnBothModuleMock);
        // downstream of moduleA are handled automatically in addModuleMock()
    }
}