/*
 * The MIT License
 *
 * Copyright 2014 Jesse Glick.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package jenkins.triggers;

import hudson.model.Cause;
import hudson.model.Computer;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.User;
import hudson.security.AuthorizationMatrixProperty;
import hudson.security.Permission;
import hudson.security.ProjectMatrixAuthorizationStrategy;
import hudson.tasks.BuildTrigger;
import hudson.tasks.BuildTriggerTest;
import hudson.triggers.Trigger;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import jenkins.model.Jenkins;
import jenkins.security.QueueItemAuthenticatorConfiguration;
import org.acegisecurity.Authentication;

import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.*;

import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockQueueItemAuthenticator;

public class ReverseBuildTriggerTest {

    @Rule public JenkinsRule r = new JenkinsRule();

    @Test public void configRoundtrip() throws Exception {
        r.createFreeStyleProject("upstream");
        FreeStyleProject downstream = r.createFreeStyleProject("downstream");
        FreeStyleProject wayDownstream = r.createFreeStyleProject("wayDownstream");
        downstream.addTrigger(new ReverseBuildTrigger("upstream", Result.SUCCESS));
        downstream.getPublishersList().add(new BuildTrigger(Collections.singleton(wayDownstream), Result.SUCCESS));
        downstream.save();
        r.configRoundtrip(downstream);
        ReverseBuildTrigger rbt = downstream.getTrigger(ReverseBuildTrigger.class);
        assertNotNull(rbt);
        assertEquals("upstream", rbt.getUpstreamProjects());
        assertEquals(Result.SUCCESS, rbt.getThreshold());
        BuildTrigger bt = downstream.getPublishersList().get(BuildTrigger.class);
        assertNotNull(bt);
        assertEquals(Collections.singletonList(wayDownstream), bt.getChildProjects(downstream));
        assertEquals(Result.SUCCESS, bt.getThreshold());
    }

    /** @see BuildTriggerTest#testDownstreamProjectSecurity */
    @Test public void upstreamProjectSecurity() throws Exception {
        r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
        ProjectMatrixAuthorizationStrategy auth = new ProjectMatrixAuthorizationStrategy();
        auth.add(Jenkins.READ, "alice");
        auth.add(Computer.BUILD, "alice");
        auth.add(Jenkins.ADMINISTER, "admin");
        auth.add(Jenkins.READ, "bob");
        auth.add(Computer.BUILD, "bob");
        r.jenkins.setAuthorizationStrategy(auth);
        String upstreamName = "upstr3@m"; // do not clash with English messages!
        final FreeStyleProject upstream = r.createFreeStyleProject(upstreamName);
        String downstreamName = "d0wnstr3am";
        FreeStyleProject downstream = r.createFreeStyleProject(downstreamName);
        Map<Permission,Set<String>> perms = new HashMap<Permission,Set<String>>();
        perms.put(Item.READ, Collections.singleton("alice"));
        downstream.addProperty(new AuthorizationMatrixProperty(perms));
        perms = new HashMap<Permission,Set<String>>();
        perms.put(Item.READ, Collections.singleton("bob"));
        upstream.addProperty(new AuthorizationMatrixProperty(perms));
        @SuppressWarnings("rawtypes") Trigger<Job> t = new ReverseBuildTrigger(upstreamName, Result.SUCCESS);
        downstream.addTrigger(t);
        t.start(downstream, true); // as in AbstractProject.submit
        r.jenkins.rebuildDependencyGraph(); // as in AbstractProject.doConfigSubmit
        assertEquals(Collections.singletonList(downstream), upstream.getDownstreamProjects());
        // TODO could check doCheckUpstreamProjects, though it is not terribly interesting
        // Legacy mode: alice has no read permission on upstream but it works anyway
        FreeStyleBuild b = r.buildAndAssertSuccess(upstream);
        r.assertLogContains(downstreamName, b);
        r.assertLogContains(hudson.tasks.Messages.BuildTrigger_warning_access_control_for_builds_in_glo(), b);
        r.waitUntilNoActivity();
        assertNotNull(JenkinsRule.getLog(b), downstream.getLastBuild());
        assertEquals(1, downstream.getLastBuild().number);
        // A QIA is configured but does not specify any authentication for downstream, so upstream should not trigger it:
        QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new MockQueueItemAuthenticator(Collections.singletonMap(upstreamName, User.get("admin").impersonate())));
        b = r.buildAndAssertSuccess(upstream);
        r.assertLogContains(downstreamName, b);
        r.assertLogContains(Messages.ReverseBuildTrigger_running_as_cannot_even_see_for_trigger_f("anonymous", upstreamName, downstreamName), b);
        r.waitUntilNoActivity();
        assertEquals(1, downstream.getLastBuild().number);
        // Auth for upstream is defined but cannot see downstream, so no message is printed about it:
        QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new MockQueueItemAuthenticator(Collections.singletonMap(upstreamName, User.get("bob").impersonate())));
        b = r.buildAndAssertSuccess(upstream);
        r.assertLogNotContains(downstreamName, b);
        r.waitUntilNoActivity();
        assertEquals(1, downstream.getLastBuild().number);
        // Alice can see upstream, so downstream gets built, but the upstream build cannot see downstream:
        perms = new HashMap<Permission,Set<String>>();
        perms.put(Item.READ, new HashSet<String>(Arrays.asList("alice", "bob")));
        upstream.removeProperty(AuthorizationMatrixProperty.class);
        upstream.addProperty(new AuthorizationMatrixProperty(perms));
        Map<String,Authentication> qiaConfig = new HashMap<String,Authentication>();
        qiaConfig.put(upstreamName, User.get("bob").impersonate());
        qiaConfig.put(downstreamName, User.get("alice").impersonate());
        QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new MockQueueItemAuthenticator(qiaConfig));
        b = r.buildAndAssertSuccess(upstream);
        r.assertLogNotContains(downstreamName, b);
        r.waitUntilNoActivity();
        assertEquals(2, downstream.getLastBuild().number);
        assertEquals(new Cause.UpstreamCause((Run) b), downstream.getLastBuild().getCause(Cause.UpstreamCause.class));
        // Now if upstream build is permitted to report on downstream:
        qiaConfig = new HashMap<String,Authentication>();
        qiaConfig.put(upstreamName, User.get("admin").impersonate());
        qiaConfig.put(downstreamName, User.get("alice").impersonate());
        QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new MockQueueItemAuthenticator(qiaConfig));
        b = r.buildAndAssertSuccess(upstream);
        r.assertLogContains(downstreamName, b);
        r.waitUntilNoActivity();
        assertEquals(3, downstream.getLastBuild().number);
        assertEquals(new Cause.UpstreamCause((Run) b), downstream.getLastBuild().getCause(Cause.UpstreamCause.class));
    }

    @Issue("JENKINS-29876")
    @Test
    public void nullJobInTriggerNotCausesNPE() throws Exception {
        final FreeStyleProject upstreamJob = r.createFreeStyleProject("upstream");

        //job with trigger.job == null
        final FreeStyleProject downstreamJob1 = r.createFreeStyleProject("downstream1");
        final ReverseBuildTrigger reverseBuildTrigger = new ReverseBuildTrigger("upstream", Result.SUCCESS);
        downstreamJob1.addTrigger(reverseBuildTrigger);
        downstreamJob1.save();

        //job with trigger.job != null
        final FreeStyleProject downstreamJob2 = r.createFreeStyleProject("downstream2");
        final ReverseBuildTrigger reverseBuildTrigger2 = new ReverseBuildTrigger("upstream", Result.SUCCESS);
        downstreamJob2.addTrigger(reverseBuildTrigger2);
        downstreamJob2.save();
        r.configRoundtrip(downstreamJob2);

        r.jenkins.rebuildDependencyGraph();
        final FreeStyleBuild build = upstreamJob.scheduleBuild2(0).get();
        r.waitUntilNoActivity();

        r.assertLogNotContains("java.lang.NullPointerException", build);
        assertThat("Build should be not triggered", downstreamJob1.getBuilds(), hasSize(0));
        assertThat("Build should be triggered", downstreamJob2.getBuilds(), not(hasSize(0)));
    }
}
