diff --git a/src/main/java/com/cdancy/jenkins/rest/binders/BindMapToForm.java b/src/main/java/com/cdancy/jenkins/rest/binders/BindMapToForm.java index 9ddf6dfb..e75be029 100644 --- a/src/main/java/com/cdancy/jenkins/rest/binders/BindMapToForm.java +++ b/src/main/java/com/cdancy/jenkins/rest/binders/BindMapToForm.java @@ -23,6 +23,7 @@ import javax.inject.Singleton; +import com.google.common.collect.Lists; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest.Builder; import org.jclouds.rest.Binder; @@ -42,11 +43,15 @@ public R bindToRequest(final R request, final Object pro if (prop.getKey() != null) { String potentialKey = prop.getKey().trim(); if (potentialKey.length() > 0) { - builder.addFormParam(potentialKey, prop.getValue().toArray(new String[prop.getValue().size()])); + if (prop.getValue() == null) { + prop.setValue(Lists.newArrayList("")); + } + + builder.addFormParam(potentialKey, prop.getValue().toArray(new String[prop.getValue().size()])); } } } return (R) builder.build(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/cdancy/jenkins/rest/domain/job/Action.java b/src/main/java/com/cdancy/jenkins/rest/domain/job/Action.java new file mode 100644 index 00000000..c82cedce --- /dev/null +++ b/src/main/java/com/cdancy/jenkins/rest/domain/job/Action.java @@ -0,0 +1,43 @@ +/* + * 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 com.cdancy.jenkins.rest.domain.job; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import org.jclouds.json.SerializedNames; + +import java.util.List; + +@AutoValue +public abstract class Action { + + public abstract List causes(); + + public abstract List parameters(); + + Action() { + } + + @SerializedNames({"causes", "parameters"}) + public static Action create(final List causes, final List parameters) { + return new AutoValue_Action( + causes != null ? ImmutableList.copyOf(causes) : ImmutableList.of(), + parameters != null ? ImmutableList.copyOf(parameters) : ImmutableList.of()); + } +} + diff --git a/src/main/java/com/cdancy/jenkins/rest/domain/job/BuildInfo.java b/src/main/java/com/cdancy/jenkins/rest/domain/job/BuildInfo.java index 173f3101..6008e061 100644 --- a/src/main/java/com/cdancy/jenkins/rest/domain/job/BuildInfo.java +++ b/src/main/java/com/cdancy/jenkins/rest/domain/job/BuildInfo.java @@ -30,6 +30,8 @@ public abstract class BuildInfo { public abstract List artifacts(); + public abstract List actions(); + public abstract boolean building(); @Nullable @@ -70,14 +72,16 @@ public abstract class BuildInfo { BuildInfo() { } - @SerializedNames({ "artifacts", "building", "description", "displayName", "duration", "estimatedDuration", + @SerializedNames({ "artifacts", "actions", "building", "description", "displayName", "duration", "estimatedDuration", "fullDisplayName", "id", "keepLog", "number", "queueId", "result", "timestamp", "url", "builtOn", "culprits" }) - public static BuildInfo create(List artifacts, boolean building, String description, String displayName, + public static BuildInfo create(List artifacts, List actions, boolean building, String description, String displayName, long duration, long estimatedDuration, String fullDisplayName, String id, boolean keepLog, int number, int queueId, String result, long timestamp, String url, String builtOn, List culprits) { return new AutoValue_BuildInfo( - artifacts != null ? ImmutableList.copyOf(artifacts) : ImmutableList. of(), building, description, - displayName, duration, estimatedDuration, fullDisplayName, id, keepLog, number, queueId, result, timestamp, - url, builtOn, culprits != null ? ImmutableList.copyOf(culprits) : ImmutableList. of()); + artifacts != null ? ImmutableList.copyOf(artifacts) : ImmutableList. of(), + actions != null ? ImmutableList.copyOf(actions) : ImmutableList. of(), + building, description, displayName, duration, estimatedDuration, fullDisplayName, + id, keepLog, number, queueId, result, timestamp, url, builtOn, + culprits != null ? ImmutableList.copyOf(culprits) : ImmutableList. of()); } } diff --git a/src/main/java/com/cdancy/jenkins/rest/domain/job/Cause.java b/src/main/java/com/cdancy/jenkins/rest/domain/job/Cause.java new file mode 100644 index 00000000..121d6a34 --- /dev/null +++ b/src/main/java/com/cdancy/jenkins/rest/domain/job/Cause.java @@ -0,0 +1,45 @@ +/* + * 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 com.cdancy.jenkins.rest.domain.job; + +import com.google.auto.value.AutoValue; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +@AutoValue +public abstract class Cause { + + @Nullable + public abstract String clazz(); + + public abstract String shortDescription(); + + @Nullable + public abstract String userId(); + + @Nullable + public abstract String userName(); + + Cause() { + } + + @SerializedNames({"_class", "shortDescription", "userId", "userName"}) + public static Cause create(final String clazz, final String shortDescription, final String userId, final String userName) { + return new AutoValue_Cause(clazz, shortDescription, userId, userName); + } +} diff --git a/src/main/java/com/cdancy/jenkins/rest/domain/job/Parameter.java b/src/main/java/com/cdancy/jenkins/rest/domain/job/Parameter.java new file mode 100644 index 00000000..4edd0ab1 --- /dev/null +++ b/src/main/java/com/cdancy/jenkins/rest/domain/job/Parameter.java @@ -0,0 +1,42 @@ +/* + * 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 com.cdancy.jenkins.rest.domain.job; + +import com.google.auto.value.AutoValue; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +@AutoValue +public abstract class Parameter { + + @Nullable + public abstract String clazz(); + + public abstract String name(); + + @Nullable + public abstract String value(); + + Parameter() { + } + + @SerializedNames({"_class", "name", "value"}) + public static Parameter create(final String clazz, final String name, final String value) { + return new AutoValue_Parameter(clazz, name, value); + } +} diff --git a/src/test/java/com/cdancy/jenkins/rest/BaseJenkinsApiLiveTest.java b/src/test/java/com/cdancy/jenkins/rest/BaseJenkinsApiLiveTest.java index a24951ae..8a14bbc4 100644 --- a/src/test/java/com/cdancy/jenkins/rest/BaseJenkinsApiLiveTest.java +++ b/src/test/java/com/cdancy/jenkins/rest/BaseJenkinsApiLiveTest.java @@ -23,6 +23,8 @@ import java.util.Properties; import java.util.UUID; +import com.cdancy.jenkins.rest.domain.queue.QueueItem; +import com.cdancy.jenkins.rest.features.QueueApi; import org.jclouds.Constants; import org.jclouds.apis.BaseApiLiveTest; import org.testng.annotations.Test; @@ -67,4 +69,30 @@ protected Iterable setupModules() { final JenkinsAuthenticationModule credsModule = new JenkinsAuthenticationModule(this.jenkinsAuthentication); return ImmutableSet. of(getLoggingModule(), credsModule); } + + /** + * Return a queue item that is being built. + * If the queue item is canceled before the build is launched, null is returned. + * To prevent the test from hanging, this method times out after 10 attempts and the queue item is returned the way it is. + * @param queueId The queue id returned when asking Jenkins to run a build. + * @return Null if the queue item has been canceled before it has had a chance to run, + * otherwise the QueueItem element is returned, but this does not guarantee that the build runs. + * The caller has to check the value of queueItem.executable, and if it is null, the queue item is still pending. + * + */ + protected QueueItem getRunningQueueItem(int queueId) throws InterruptedException { + int max = 10; + QueueItem queueItem = api.queueApi().queueItem(queueId); + while (max > 0) { + if (queueItem.cancelled()) return null; + if (queueItem.executable() != null) { + return queueItem; + } + Thread.sleep(2000); + queueItem = api.queueApi().queueItem(queueId); + max = max - 1; + } + return queueItem; + } + } diff --git a/src/test/java/com/cdancy/jenkins/rest/features/JobsApiLiveTest.java b/src/test/java/com/cdancy/jenkins/rest/features/JobsApiLiveTest.java index 66f454d9..8a4da992 100644 --- a/src/test/java/com/cdancy/jenkins/rest/features/JobsApiLiveTest.java +++ b/src/test/java/com/cdancy/jenkins/rest/features/JobsApiLiveTest.java @@ -25,8 +25,11 @@ import java.util.List; import java.util.Map; +import com.cdancy.jenkins.rest.domain.job.Cause; +import com.cdancy.jenkins.rest.domain.job.Parameter; import com.cdancy.jenkins.rest.domain.plugins.Plugin; import com.cdancy.jenkins.rest.domain.plugins.Plugins; +import com.cdancy.jenkins.rest.domain.queue.QueueItem; import org.testng.annotations.Test; import com.cdancy.jenkins.rest.BaseJenkinsApiLiveTest; @@ -119,6 +122,12 @@ public void testGetBuildInfo() { } @Test(dependsOnMethods = "testGetBuildInfo") + public void testGetBuildParametersOfLastJob() { + List parameters = api().buildInfo(null, "DevTest", 1).actions().get(0).parameters(); + assertTrue(parameters.size() == 0); + } + + @Test(dependsOnMethods = "testGetBuildParametersOfLastJob") public void testCreateJobThatAlreadyExists() { String config = payloadFromResource("/freestyle-project.xml"); RequestStatus success = api().create(null, "DevTest", config); @@ -277,12 +286,14 @@ public void testGetJobInfoInFolder() { } @Test(dependsOnMethods = "testGetJobInfoInFolder") - public void testBuildWithParameters() { + public void testBuildWithParameters() throws InterruptedException { Map> params = new HashMap<>(); params.put("SomeKey", Lists.newArrayList("SomeVeryNewValue")); queueIdForAnotherJob = api().buildWithParameters("test-folder/test-folder-1", "JobInFolder", params); assertNotNull(queueIdForAnotherJob); assertTrue(queueIdForAnotherJob.value() > 0); + QueueItem queueItem = getRunningQueueItem(queueIdForAnotherJob.value()); + assertNotNull(queueItem); } @Test(dependsOnMethods = "testBuildWithParameters") @@ -307,6 +318,59 @@ public void testGetBuildInfoOfJobInFolder() { assertTrue(output.queueId() == queueIdForAnotherJob.value()); } + @Test(dependsOnMethods = "testGetProgressiveText") + public void testGetBuildParametersofJob() { + List parameters = api().buildInfo("test-folder/test-folder-1", "JobInFolder",1).actions().get(0).parameters(); + assertNotNull(parameters); + assertTrue(parameters.get(0).name().equals("SomeKey")); + assertTrue(parameters.get(0).value().equals("SomeVeryNewValue")); + } + + @Test(dependsOnMethods = "testGetProgressiveText") + public void testGetBuildCausesOfJob() { + List causes = api().buildInfo("test-folder/test-folder-1", "JobInFolder",1).actions().get(1).causes(); + assertNotNull(causes); + assertTrue(causes.size() > 0); + assertNotNull(causes.get(0).shortDescription()); + assertNotNull(causes.get(0).userId()); + assertNotNull(causes.get(0).userName()); + } + + public void testCreateJobForEmptyAndNullParams() { + String config = payloadFromResource("/freestyle-project-empty-and-null-params.xml"); + RequestStatus success = api().create(null, "JobForEmptyAndNullParams", config); + assertTrue(success.value()); + } + + @Test(dependsOnMethods = "testCreateJobForEmptyAndNullParams") + public void testBuildWithParametersOfJobForEmptyAndNullParams() throws InterruptedException { + Map> params = new HashMap<>(); + params.put("SomeKey1", Lists.newArrayList("")); + params.put("SomeKey2", null); + IntegerResponse job1 = api.jobsApi().buildWithParameters(null, "JobForEmptyAndNullParams", params); + assertNotNull(job1); + assertTrue(job1.value() > 0); + assertTrue(job1.errors().size() == 0); + QueueItem queueItem = getRunningQueueItem(job1.value()); + assertNotNull(queueItem); + } + + @Test(dependsOnMethods = "testBuildWithParametersOfJobForEmptyAndNullParams") + public void testGetBuildParametersOfJobForEmptyAndNullParams() { + List parameters = api().buildInfo(null, "JobForEmptyAndNullParams", 1).actions().get(0).parameters(); + assertNotNull(parameters); + assertTrue(parameters.get(0).name().equals("SomeKey1")); + assertTrue(parameters.get(0).value().isEmpty()); + assertTrue(parameters.get(1).name().equals("SomeKey2")); + assertTrue(parameters.get(1).value().isEmpty()); + } + + @Test(dependsOnMethods = "testGetBuildParametersOfJobForEmptyAndNullParams") + public void testDeleteJobForEmptyAndNullParams() { + RequestStatus success = api().delete(null, "JobForEmptyAndNullParams"); + assertTrue(success.value()); + } + @Test(dependsOnMethods = "testCreateFoldersInJenkins") public void testCreateJobWithLeadingAndTrailingForwardSlashes() { String config = payloadFromResource("/freestyle-project-no-params.xml"); diff --git a/src/test/java/com/cdancy/jenkins/rest/features/JobsApiMockTest.java b/src/test/java/com/cdancy/jenkins/rest/features/JobsApiMockTest.java index b8163ca0..6e7f7ae0 100644 --- a/src/test/java/com/cdancy/jenkins/rest/features/JobsApiMockTest.java +++ b/src/test/java/com/cdancy/jenkins/rest/features/JobsApiMockTest.java @@ -21,12 +21,16 @@ import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.MediaType; +import com.cdancy.jenkins.rest.domain.job.Action; +import com.cdancy.jenkins.rest.domain.job.Cause; +import com.cdancy.jenkins.rest.domain.job.Parameter; import org.testng.annotations.Test; import com.cdancy.jenkins.rest.JenkinsApi; @@ -536,6 +540,85 @@ public void testBuildJobWithParamsNonExistentJob() throws Exception { } } + public void testGetParams() throws Exception { + MockWebServer server = mockWebServer(); + + String body = payloadFromResource("/build-info.json"); + server.enqueue(new MockResponse().setBody(body).setResponseCode(200)); + JenkinsApi jenkinsApi = api(server.getUrl("/")); + JobsApi api = jenkinsApi.jobsApi(); + try { + List output = api.buildInfo(null,"fish", 10).actions().get(0).parameters(); + assertNotNull(output); + assertTrue(output.get(0).name().equals("bear")); + assertTrue(output.get(0).value().equals("true")); + assertTrue(output.get(1).name().equals("fish")); + assertTrue(output.get(1).value().equals("salmon")); + assertSent(server, "GET", "/job/fish/10/api/json"); + } finally { + jenkinsApi.close(); + server.shutdown(); + } + } + + public void testGetParamsWhenNoBuildParams() throws Exception { + MockWebServer server = mockWebServer(); + + String body = payloadFromResource("/build-info-no-params.json"); + server.enqueue(new MockResponse().setBody(body).setResponseCode(200)); + JenkinsApi jenkinsApi = api(server.getUrl("/")); + JobsApi api = jenkinsApi.jobsApi(); + try { + List output = api.buildInfo(null,"fish", 10).actions().get(0).parameters(); + assertTrue(output.size() == 0); + assertSent(server, "GET", "/job/fish/10/api/json"); + } finally { + jenkinsApi.close(); + server.shutdown(); + } + } + + public void testGetParamsWhenEmptyorNullParams() throws Exception { + MockWebServer server = mockWebServer(); + + String body = payloadFromResource("/build-info-empty-and-null-params.json"); + server.enqueue(new MockResponse().setBody(body).setResponseCode(200)); + JenkinsApi jenkinsApi = api(server.getUrl("/")); + JobsApi api = jenkinsApi.jobsApi(); + try { + List output = api.buildInfo(null,"fish", 10).actions().get(0).parameters(); + assertNotNull(output); + assertTrue(output.get(0).name().equals("bear")); + assertTrue(output.get(0).value().equals("null")); + assertTrue(output.get(1).name().equals("fish")); + assertTrue(output.get(1).value().isEmpty()); + assertSent(server, "GET", "/job/fish/10/api/json"); + } finally { + jenkinsApi.close(); + server.shutdown(); + } + } + + public void testGetCause() throws Exception { + MockWebServer server = mockWebServer(); + + String body = payloadFromResource("/build-info-no-params.json"); + server.enqueue(new MockResponse().setBody(body).setResponseCode(200)); + JenkinsApi jenkinsApi = api(server.getUrl("/")); + JobsApi api = jenkinsApi.jobsApi(); + try { + List output = api.buildInfo(null,"fish", 10).actions().get(0).causes(); + assertNotNull(output); + assertTrue(output.get(0).shortDescription().equals("Started by user anonymous")); + assertNull(output.get(0).userId()); + assertTrue(output.get(0).userName().equals("anonymous")); + assertSent(server, "GET", "/job/fish/10/api/json"); + } finally { + jenkinsApi.close(); + server.shutdown(); + } + } + public void testGetLastBuildNumber() throws Exception { MockWebServer server = mockWebServer(); diff --git a/src/test/java/com/cdancy/jenkins/rest/features/QueueApiLiveTest.java b/src/test/java/com/cdancy/jenkins/rest/features/QueueApiLiveTest.java index 0b8c2e32..e6adc901 100644 --- a/src/test/java/com/cdancy/jenkins/rest/features/QueueApiLiveTest.java +++ b/src/test/java/com/cdancy/jenkins/rest/features/QueueApiLiveTest.java @@ -220,31 +220,6 @@ public void testCancelNonExistentQueueItem() throws InterruptedException { assertTrue(success.errors().isEmpty()); } - /** - * Return a queue item that is being built. - * If the queue item is canceled before the build is launched, null is returned. - * To prevent the test from hanging, this method times out after 10 attempts and the queue item is returned the way it is. - * @param queueId The queue id returned when asking Jenkins to run a build. - * @return Null if the queue item has been canceled before it has had a chance to run, - * otherwise the QueueItem element is returned, but this does not guarantee that the build runs. - * The caller has to check the value of queueItem.executable, and if it is null, the queue item is still pending. - * - */ - private QueueItem getRunningQueueItem(int queueId) throws InterruptedException { - int max = 10; - QueueItem queueItem = api().queueItem(queueId); - while (max > 0) { - if (queueItem.cancelled()) return null; - if (queueItem.executable() != null) { - return queueItem; - } - Thread.sleep(2000); - queueItem = api().queueItem(queueId); - max = max - 1; - } - return queueItem; - } - @AfterClass public void finish() { RequestStatus success = api.jobsApi().delete(null,"QueueTest"); diff --git a/src/test/resources/build-info-empty-and-null-params.json b/src/test/resources/build-info-empty-and-null-params.json new file mode 100644 index 00000000..2340ef11 --- /dev/null +++ b/src/test/resources/build-info-empty-and-null-params.json @@ -0,0 +1,59 @@ +{ + "actions" : [ + { + "parameters" : [ + { + "name" : "bear", + "value" : "null" + }, + { + "name" : "fish", + "value" : "" + } + ] + }, + { + "causes" : [ + { + "shortDescription" : "Started by user anonymous", + "userId" : null, + "userName" : "anonymous" + } + ] + } + ], + "artifacts" : [ + { + "displayPath" : "fish.txt", + "fileName" : "fish.txt", + "relativePath" : "fish.txt" + } + ], + "building" : false, + "description" : null, + "displayName" : "#10", + "duration" : 60750, + "estimatedDuration" : 60258, + "executor" : null, + "fullDisplayName" : "fish #10", + "id" : "10", + "keepLog" : false, + "number" : 10, + "queueId" : 73, + "result" : "SUCCESS", + "timestamp" : 1461091892486, + "url" : "http://localhost:32769/job/fish/10/", + "builtOn" : "", + "changeSet" : { + "items" : [ + + ], + "kind" : null + }, + "culprits" : [ + { + "absoluteUrl": "http://localhost:8080/user/username", + "fullName": "username" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/build-info-no-params.json b/src/test/resources/build-info-no-params.json new file mode 100644 index 00000000..0047971d --- /dev/null +++ b/src/test/resources/build-info-no-params.json @@ -0,0 +1,70 @@ +{ + "_class" : "org.jenkinsci.plugins.workflow.job.WorkflowRun", + "actions" : [ + { + "_class" : "hudson.model.CauseAction", + "causes" : [ + { + "_class" : "hudson.model.Cause$UserIdCause", + "shortDescription" : "Started by user anonymous", + "userId" : null, + "userName" : "anonymous" + } + ] + }, + { + "_class" : "jenkins.metrics.impl.TimeInQueueAction" + }, + { + + }, + { + + }, + { + + }, + { + + }, + { + "_class" : "org.jenkinsci.plugins.workflow.job.views.FlowGraphAction" + }, + { + + }, + { + + } + ], + "artifacts" : [ + + ], + "building" : false, + "description" : null, + "displayName" : "#10", + "duration" : 60750, + "estimatedDuration" : 60258, + "executor" : null, + "fullDisplayName" : "fish #10", + "id" : "10", + "keepLog" : false, + "number" : 10, + "queueId" : 73, + "result" : "SUCCESS", + "timestamp" : 1461091892486, + "url" : "http://localhost:32769/job/fish/10/", + "builtOn" : "", + "changeSet" : { + "items" : [ + + ], + "kind" : null + }, + "culprits" : [ + { + "absoluteUrl": "http://localhost:8080/user/username", + "fullName": "username" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/freestyle-project-empty-and-null-params.xml b/src/test/resources/freestyle-project-empty-and-null-params.xml new file mode 100644 index 00000000..a8f8be3e --- /dev/null +++ b/src/test/resources/freestyle-project-empty-and-null-params.xml @@ -0,0 +1,30 @@ + + + + HelloWorld + false + + + + + SomeKey1 + whatever1 + + + SomeKey2 + whatever2 + + + + + + true + false + false + false + + false + + + +