diff --git a/core/src/main/java/jenkins/widgets/HistoryPageFilter.java b/core/src/main/java/jenkins/widgets/HistoryPageFilter.java index 04938ec9a666c1142d77089039f88f8a6e52ff58..269f5e44fc45ba5c3c718a1d9825ba9054adc19e 100644 --- a/core/src/main/java/jenkins/widgets/HistoryPageFilter.java +++ b/core/src/main/java/jenkins/widgets/HistoryPageFilter.java @@ -25,9 +25,13 @@ package jenkins.widgets; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; +import hudson.model.AbstractBuild; import hudson.model.Job; +import hudson.model.ParameterValue; +import hudson.model.ParametersAction; import hudson.model.Queue; import hudson.model.Run; +import hudson.search.UserSearchProperty; import hudson.widgets.HistoryWidget; import javax.annotation.Nonnull; @@ -37,6 +41,7 @@ import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * History page filter. @@ -52,8 +57,8 @@ public class HistoryPageFilter { // Need to use different Lists for Queue.Items and Runs because // we need access to them separately in the jelly files for rendering. - public final List> queueItems = new ArrayList>(); - public final List> runs = new ArrayList>(); + public final List> queueItems = new ArrayList<>(); + public final List> runs = new ArrayList<>(); public boolean hasUpPage = false; // there are newer builds than on this page public boolean hasDownPage = false; // there are older builds than on this page @@ -343,6 +348,13 @@ public class HistoryPageFilter { return true; } else if (fitsSearchString(run.getResult())) { return true; + } else if (run instanceof AbstractBuild && fitsSearchBuildVariables((AbstractBuild) run)) { + return true; + } else { + ParametersAction parametersAction = run.getAction(ParametersAction.class); + if (parametersAction != null && fitsSearchBuildParameters(parametersAction)) { + return true; + } } // Non of the fuzzy matches "liked" the search term. @@ -354,14 +366,38 @@ public class HistoryPageFilter { return true; } - if (data != null) { - if (data instanceof Number) { - return data.toString().equals(searchString); + if (data == null) { + return false; + } + + if (data instanceof Number) { + return data.toString().equals(searchString); + } else { + if (UserSearchProperty.isCaseInsensitive()) { + return data.toString().toLowerCase().contains(searchString.toLowerCase()); } else { - return data.toString().toLowerCase().contains(searchString); + return data.toString().contains(searchString); + } + } + } + + private boolean fitsSearchBuildVariables(AbstractBuild runAsBuild) { + Map buildVariables = runAsBuild.getBuildVariables(); + for (String paramsValues : buildVariables.values()) { + if (fitsSearchString(paramsValues)) { + return true; } } - return false; - } + } + + private boolean fitsSearchBuildParameters(ParametersAction parametersAction) { + List parameters = parametersAction.getParameters(); + for (ParameterValue parameter : parameters) { + if (!parameter.isSensitive() && fitsSearchString(parameter.getValue())) { + return true; + } + } + return false; + } } diff --git a/core/src/test/java/jenkins/widgets/HistoryPageFilterTest.java b/core/src/test/java/jenkins/widgets/HistoryPageFilterTest.java index f00969b769df4ae8b3629985529a7962d4a766b5..9649264d0a0fe36574c2fb05542d3c9d291968ce 100644 --- a/core/src/test/java/jenkins/widgets/HistoryPageFilterTest.java +++ b/core/src/test/java/jenkins/widgets/HistoryPageFilterTest.java @@ -23,24 +23,39 @@ */ package jenkins.widgets; +import hudson.model.Build; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; import hudson.model.Job; import hudson.model.MockItem; import hudson.model.ModelObject; +import hudson.model.ParameterValue; +import hudson.model.ParametersAction; import hudson.model.Queue; import hudson.model.Result; import hudson.model.Run; -import jenkins.widgets.HistoryPageEntry; -import jenkins.widgets.HistoryPageFilter; +import hudson.model.StringParameterValue; + import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import java.io.IOException; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; /** * @author tom.fennelly@gmail.com + * + * See also HistoryPageFilterInsensitiveSearchTest integration test. */ public class HistoryPageFilterTest { @@ -293,6 +308,80 @@ public class HistoryPageFilterTest { Assert.assertEquals(HistoryPageEntry.getEntryId(6), historyPageFilter.oldestOnPage); } + @Test + public void test_search_runs_by_build_number() throws IOException { + //given + HistoryPageFilter historyPageFilter = newPage(5, null, null); + List runs = newRuns(23, 24); + List queueItems = newQueueItems(25, 26); + //and + historyPageFilter.setSearchString("23"); + + //when + historyPageFilter.add(runs, queueItems); + + //then + Assert.assertEquals(1, historyPageFilter.runs.size()); + Assert.assertEquals(HistoryPageEntry.getEntryId(23), historyPageFilter.runs.get(0).getEntryId()); + } + + @Test + public void test_search_should_be_case_sensitive_for_anonymous_user() throws IOException { + //given + HistoryPageFilter historyPageFilter = newPage(5, null, null); + //and + historyPageFilter.setSearchString("failure"); + //and + List runs = Lists.newArrayList(new MockRun(2, Result.FAILURE), new MockRun(1, Result.SUCCESS)); + List queueItems = newQueueItems(3, 4); + + //when + historyPageFilter.add(runs, queueItems); + + //then + Assert.assertEquals(0, historyPageFilter.runs.size()); + } + + @Test + public void test_search_builds_by_build_variables() throws IOException { + List runs = ImmutableList.of( + new MockBuild(2).withBuildVariables(ImmutableMap.of("env", "dummyEnv")), + new MockBuild(1).withBuildVariables(ImmutableMap.of("env", "otherEnv"))); + assertOneMatchingBuildForGivenSearchStringAndRunItems("dummyEnv", runs); + } + + @Test + public void test_search_builds_by_build_params() throws IOException { + List runs = ImmutableList.of( + new MockBuild(2).withBuildParameters(ImmutableMap.of("env", "dummyEnv")), + new MockBuild(1).withBuildParameters(ImmutableMap.of("env", "otherEnv"))); + assertOneMatchingBuildForGivenSearchStringAndRunItems("dummyEnv", runs); + } + + @Test + public void test_ignore_sensitive_parameters_in_search_builds_by_build_params() throws IOException { + List runs = ImmutableList.of( + new MockBuild(2).withBuildParameters(ImmutableMap.of("plainPassword", "pass1plain")), + new MockBuild(1).withSensitiveBuildParameters("password", "pass1")); + assertOneMatchingBuildForGivenSearchStringAndRunItems("pass1", runs); + } + + private void assertOneMatchingBuildForGivenSearchStringAndRunItems(String searchString, List runs) { + //given + HistoryPageFilter historyPageFilter = newPage(5, null, null); + //and + historyPageFilter.setSearchString(searchString); + //and + List queueItems = newQueueItems(3, 4); + + //when + historyPageFilter.add(runs, queueItems); + + //then + Assert.assertEquals(1, historyPageFilter.runs.size()); + Assert.assertEquals(HistoryPageEntry.getEntryId(2), historyPageFilter.runs.get(0).getEntryId()); + } + private List newQueueItems(long startId, long endId) { List items = new ArrayList<>(); for (long queueId = startId; queueId <= endId; queueId++) { @@ -329,6 +418,11 @@ public class HistoryPageFilterTest { this.queueId = queueId; } + public MockRun(long queueId, Result result) throws IOException { + this(queueId); + this.result = result; + } + @Override public int compareTo(Run o) { return 0; @@ -373,4 +467,60 @@ public class HistoryPageFilterTest { return super.getNumber(); } } + + private static class MockBuild extends Build { + + private final int buildNumber; + + private Map buildVariables = Collections.emptyMap(); + + private MockBuild(int buildNumber) { + super(Mockito.mock(FreeStyleProject.class), Mockito.mock(Calendar.class)); + this.buildNumber = buildNumber; + } + + @Override + public int getNumber() { + return buildNumber; + } + + @Override + public Map getBuildVariables() { + return buildVariables; + } + + MockBuild withBuildVariables(Map buildVariables) { + this.buildVariables = buildVariables; + return this; + } + + MockBuild withBuildParameters(Map buildParametersAsMap) throws IOException { + addAction(new ParametersAction(buildPropertiesMapToParameterValues(buildParametersAsMap), buildParametersAsMap.keySet())); + return this; + } + + //TODO: Rewrite in functional style when Java 8 is available + private List buildPropertiesMapToParameterValues(Map buildParametersAsMap) { + List parameterValues = new ArrayList<>(); + for (Map.Entry parameter : buildParametersAsMap.entrySet()) { + parameterValues.add(new StringParameterValue(parameter.getKey(), parameter.getValue())); + } + return parameterValues; + } + + MockBuild withSensitiveBuildParameters(String paramName, String paramValue) throws IOException { + addAction(new ParametersAction(ImmutableList.of(createSensitiveStringParameterValue(paramName, paramValue)), + ImmutableList.of(paramName))); + return this; + } + + private StringParameterValue createSensitiveStringParameterValue(final String paramName, final String paramValue) { + return new StringParameterValue(paramName, paramValue) { + @Override + public boolean isSensitive() { + return true; + } + }; + } + } } diff --git a/test/src/test/java/jenkins/widgets/HistoryPageFilterInsensitiveSearchTest.java b/test/src/test/java/jenkins/widgets/HistoryPageFilterInsensitiveSearchTest.java new file mode 100644 index 0000000000000000000000000000000000000000..41e821b69596807e66e9a7a447bf2009b20918b4 --- /dev/null +++ b/test/src/test/java/jenkins/widgets/HistoryPageFilterInsensitiveSearchTest.java @@ -0,0 +1,114 @@ +package jenkins.widgets; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.mockito.Mockito; + +import com.google.common.collect.ImmutableList; +import hudson.model.Job; +import hudson.model.ModelObject; +import hudson.model.Queue; +import hudson.model.Result; +import hudson.model.Run; +import hudson.model.User; +import hudson.search.UserSearchProperty; +import hudson.security.ACL; +import hudson.security.ACLContext; +import hudson.security.AuthorizationStrategy; + +/** + * TODO: Code partially duplicated with HistoryPageFilterTest in core + */ +public class HistoryPageFilterInsensitiveSearchTest { + + private static final String TEST_USER_NAME = "testUser"; + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void should_search_insensitively_when_enabled_for_user() throws IOException { + setUserContextAndAssertCaseInsensitivitySearchForGivenSearchString("failure"); + } + + @Test + public void should_also_lower_search_query_in_insensitive_search_enabled() throws IOException { + setUserContextAndAssertCaseInsensitivitySearchForGivenSearchString("FAILure"); + } + + private void setUserContextAndAssertCaseInsensitivitySearchForGivenSearchString(final String searchString) throws IOException { + AuthorizationStrategy.Unsecured strategy = new AuthorizationStrategy.Unsecured(); + j.jenkins.setAuthorizationStrategy(strategy); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + + UsernamePasswordAuthenticationToken testUserAuthentication = new UsernamePasswordAuthenticationToken(TEST_USER_NAME, "any"); + try (ACLContext acl = ACL.as(testUserAuthentication)) { + User.get(TEST_USER_NAME).addProperty(new UserSearchProperty(true)); + + //test logic + List runs = ImmutableList.of(new MockRun(2, Result.FAILURE), new MockRun(1, Result.SUCCESS)); + assertOneMatchingBuildForGivenSearchStringAndRunItems(searchString, runs); + } + } + + private void assertOneMatchingBuildForGivenSearchStringAndRunItems(String searchString, List runs) { + //given + HistoryPageFilter historyPageFilter = new HistoryPageFilter<>(5); + //and + historyPageFilter.setSearchString(searchString); + + //when + historyPageFilter.add(runs, Collections.emptyList()); + + //then + Assert.assertEquals(1, historyPageFilter.runs.size()); + Assert.assertEquals(HistoryPageEntry.getEntryId(2), historyPageFilter.runs.get(0).getEntryId()); + } + + @SuppressWarnings("unchecked") + private static class MockRun extends Run { + private final long queueId; + + public MockRun(long queueId) throws IOException { + super(Mockito.mock(Job.class)); + this.queueId = queueId; + } + + public MockRun(long queueId, Result result) throws IOException { + this(queueId); + this.result = result; + } + + @Override + public int compareTo(Run o) { + return 0; + } + + @Override + public Result getResult() { + return result; + } + + @Override + public boolean isBuilding() { + return false; + } + + @Override + public long getQueueId() { + return queueId; + } + + @Override + public int getNumber() { + return (int) queueId; + } + } +}