From 5c0f54274fe1533a427da9a9829085ee6f942b08 Mon Sep 17 00:00:00 2001 From: Ronny Perinke <23166289+sephiroth-j@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:20:53 +0100 Subject: [PATCH] support isLatest flag closes #286 --- CHANGELOG.md | 3 + README.md | 10 +++- .../plugins/DependencyTrack/ApiClient.java | 33 +++++++---- .../DependencyTrackPublisher.java | 6 +- .../DependencyTrack/DescriptorImpl.java | 2 +- .../DependencyTrack/ProjectProperties.java | 8 +++ .../ProjectProperties/config.jelly | 3 + .../ProjectProperties/config.properties | 3 +- .../ProjectProperties/config_de.properties | 3 +- .../ProjectProperties/help-isLatest.html | 3 + .../ProjectProperties/help-isLatest_de.html | 3 + .../DependencyTrack/ApiClientTest.java | 33 +++++++---- .../DependencyTrackPublisherTest.java | 56 ++++++++++++------- .../DependencyTrack/DescriptorImplTest.java | 18 +++--- 14 files changed, 126 insertions(+), 58 deletions(-) create mode 100644 src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/help-isLatest.html create mode 100644 src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/help-isLatest_de.html diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e25fa..96213dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,11 @@ ### ⚠ Breaking - require Jenkins 2.479.1 or newer - require Java 17 or newer (required since Jenkins 2.479.1) +- require Dependency-Track 4.12 or newer ([#286](https://github.com/jenkinsci/dependency-track-plugin/issues/286)) ### ⭐ New Features +- Support "isLatest" flag ([#286](https://github.com/jenkinsci/dependency-track-plugin/issues/286)) + ### 🐞 Bugs Fixed ## v5.2.0 - 2024-12-08 diff --git a/README.md b/README.md index d1f049d..a0de150 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ Asynchronous publishing simply uploads the SBOM to Dependency-Track and the job ![build summary](docs/images/jenkins-build-summary.png) ![findings](docs/images/jenkins-build-findings.png) ![policy violations](docs/images/jenkins-build-policy-violations.png) +## Version Compatibility Matrix +Plugin Version | Dependency-Track | Jenkins | Java +---------------| ---------------- | ------- | ---- +6.0.x (next) | 4.12+ | 2.479.1+ | 17+ +5.2.x (current) | 4.9+ | 2.440.1+ | 11+ + ## Global Configuration To setup, navigate to Jenkins > System Configuration and complete the Dependency-Track section. @@ -75,7 +81,9 @@ Once configured with a valid URL and API key, simply configure a job to publish - SWID tag ID - group/vendor - description -- ID of parent project (for Dependency-Track v4.7 and newer) +- ID of parent project +- name and version of parent project (as an alternative to the ID) +- "is latest version" flag The use of environment variables in the form `${VARIABLE}` is supported here. diff --git a/src/main/java/org/jenkinsci/plugins/DependencyTrack/ApiClient.java b/src/main/java/org/jenkinsci/plugins/DependencyTrack/ApiClient.java index ba0c18d..85c964f 100644 --- a/src/main/java/org/jenkinsci/plugins/DependencyTrack/ApiClient.java +++ b/src/main/java/org/jenkinsci/plugins/DependencyTrack/ApiClient.java @@ -291,8 +291,7 @@ public List getViolations(@NonNull final String projectUuid) throws A @NonNull @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") - public UploadResult upload(@Nullable final String projectId, @Nullable final String projectName, @Nullable final String projectVersion, @NonNull final FilePath artifact, - boolean autoCreateProject, @Nullable final ProjectProperties properties) throws ApiClientException { + public UploadResult upload(@NonNull final ProjectData project, @NonNull final FilePath artifact) throws ApiClientException { final String encodedScan; try (var in = artifact.read()) { encodedScan = Base64.getEncoder().encodeToString(in.readAllBytes()); @@ -303,17 +302,19 @@ public UploadResult upload(@Nullable final String projectId, @Nullable final Str // Creates the JSON payload that will be sent to Dependency-Track final var bomSubmitRequest = new JSONObject(); bomSubmitRequest.element("bom", encodedScan); - if (StringUtils.isNotBlank(projectId)) { - bomSubmitRequest.element("project", projectId); + if (StringUtils.isNotBlank(project.id())) { + bomSubmitRequest.element("project", project.id()); } else { - bomSubmitRequest.element("projectName", projectName) - .element("projectVersion", projectVersion) - .element("autoCreate", autoCreateProject); + bomSubmitRequest.element("projectName", project.name()) + .element("projectVersion", project.version()) + .element("autoCreate", project.autoCreate()); } + final var properties = project.properties(); if (properties != null) { bomSubmitRequest.elementOpt("parentUUID", properties.getParentId()) .elementOpt("parentName", properties.getParentName()) - .elementOpt("parentVersion", properties.getParentVersion()); + .elementOpt("parentVersion", properties.getParentVersion()) + .elementOpt("isLatestProjectVersion", properties.getIsLatest()); } final var request = createRequest(URI.create(BOM_URL), "PUT", RequestBody.create(bomSubmitRequest.toString(), APPLICATION_JSON)); return executeWithRetry(() -> { @@ -365,14 +366,18 @@ public void updateProjectProperties(@NonNull final String projectUuid, @NonNull updates.elementOpt("group", properties.getGroup()); // overwrite description only if it is set (means not null) updates.elementOpt("description", properties.getDescription()); + // overwrite isLatest only if it is set (means not null) + updates.elementOpt("isLatest", properties.getIsLatest()); // set new parent project if it is set (means not null) if (properties.getParentId() != null) { JSONObject newParent = new JSONObject().elementOpt("uuid", properties.getParentId()); updates.element("parent", newParent); } - // update project - updateProject(projectUuid, updates); + // update project if necessary + if (!updates.isEmpty()) { + updateProject(projectUuid, updates); + } } @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") @@ -450,4 +455,12 @@ private interface RetryAction { T doWithRetry() throws E; } + + static record ProjectData(@Nullable String id, + @Nullable String name, + @Nullable String version, + boolean autoCreate, + @Nullable ProjectProperties properties) { + + } } diff --git a/src/main/java/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisher.java b/src/main/java/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisher.java index e807013..6e778c9 100644 --- a/src/main/java/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisher.java +++ b/src/main/java/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisher.java @@ -322,8 +322,8 @@ public void perform(@NonNull final Run run, @NonNull final FilePath worksp final ProjectProperties effectiveProjectProperties = expandProjectProperties(env); logger.log(Messages.Builder_Publishing(effectiveUrl, effectiveArtifact)); final ApiClient apiClient = clientFactory.create(effectiveUrl, effectiveApiKey, logger, getEffectiveConnectionTimeout(), getEffectiveReadTimeout()); - final UploadResult uploadResult = apiClient.upload(projectId, effectiveProjectName, effectiveProjectVersion, - artifactFilePath, effectiveAutocreate, effectiveProjectProperties); + final var projectData = new ApiClient.ProjectData(projectId, effectiveProjectName, effectiveProjectVersion, effectiveAutocreate, effectiveProjectProperties); + final UploadResult uploadResult = apiClient.upload(projectData, artifactFilePath); if (!uploadResult.isSuccess()) { throw new AbortException(Messages.Builder_Upload_Failed()); @@ -615,6 +615,7 @@ private void updateProjectProperties(final ConsoleLogger logger, final ApiClient projectProperties.getDescription() != null || projectProperties.getGroup() != null || projectProperties.getSwidTagId() != null + || projectProperties.getIsLatest() != null || !projectProperties.getTags().isEmpty()); if (doUpdateProject) { @@ -646,6 +647,7 @@ private ProjectProperties expandProjectProperties(final EnvVars env) { Optional.ofNullable(projectProperties.getParentVersion()).map(env::expand).ifPresent(expandedProperties::setParentVersion); Optional.ofNullable(projectProperties.getSwidTagId()).map(env::expand).ifPresent(expandedProperties::setSwidTagId); expandedProperties.setTags(projectProperties.getTags().stream().map(env::expand).toList()); + expandedProperties.setIsLatest(projectProperties.getIsLatest()); return expandedProperties; } return null; diff --git a/src/main/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImpl.java b/src/main/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImpl.java index a557b9c..cbc0803 100644 --- a/src/main/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImpl.java +++ b/src/main/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImpl.java @@ -273,7 +273,7 @@ private FormValidation testConnection(final String dependencyTrackUrl, final Str return FormValidation.error(Messages.Publisher_ConnectionTest_Error(poweredBy)); } final VersionNumber version = apiClient.getVersion(); - final var requiredVersion = new VersionNumber("4.9.0"); + final var requiredVersion = new VersionNumber("4.12.0"); if (version.isOlderThan(requiredVersion)) { return FormValidation.error(Messages.Publisher_ConnectionTest_VersionWarning(version, requiredVersion)); } diff --git a/src/main/java/org/jenkinsci/plugins/DependencyTrack/ProjectProperties.java b/src/main/java/org/jenkinsci/plugins/DependencyTrack/ProjectProperties.java index dc6e3a2..cba4a6e 100644 --- a/src/main/java/org/jenkinsci/plugins/DependencyTrack/ProjectProperties.java +++ b/src/main/java/org/jenkinsci/plugins/DependencyTrack/ProjectProperties.java @@ -31,6 +31,7 @@ import jenkins.model.Jenkins; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.Setter; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; @@ -93,6 +94,13 @@ public final class ProjectProperties extends AbstractDescribableImpl getTags() { return normalizeTags(tags); diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config.jelly b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config.jelly index 1d876fb..1e1aeda 100644 --- a/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config.jelly @@ -28,6 +28,9 @@ limitations under the License. + + + diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config.properties b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config.properties index 9e4ef7b..b7f83e5 100644 --- a/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config.properties +++ b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config.properties @@ -18,4 +18,5 @@ group=Namespace / Group / Vendor description=Description parentId=Parent project parentName=Parent name -parentVersion=Parent version \ No newline at end of file +parentVersion=Parent version +isLatest=Is latest version \ No newline at end of file diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config_de.properties b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config_de.properties index ed9047f..f2ff92e 100644 --- a/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config_de.properties +++ b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/config_de.properties @@ -18,4 +18,5 @@ group=Namensraum / Gruppe / Hersteller description=Beschreibung parentId=\u00dcbergeordnetes Projekt parentName=Name des \u00fcbergeordneten Projekts -parentVersion=Version des \u00fcbergeordneten Projekts \ No newline at end of file +parentVersion=Version des \u00fcbergeordneten Projekts +isLatest=Ist aktuellste Version \ No newline at end of file diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/help-isLatest.html b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/help-isLatest.html new file mode 100644 index 0000000..da960db --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/help-isLatest.html @@ -0,0 +1,3 @@ +
+

Marks this version of the project as the latest.

+
\ No newline at end of file diff --git a/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/help-isLatest_de.html b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/help-isLatest_de.html new file mode 100644 index 0000000..32316eb --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/DependencyTrack/ProjectProperties/help-isLatest_de.html @@ -0,0 +1,3 @@ +
+

Markiert diese Version des Projekts als die neueste.

+
\ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/DependencyTrack/ApiClientTest.java b/src/test/java/org/jenkinsci/plugins/DependencyTrack/ApiClientTest.java index 81bc6a1..162eb94 100644 --- a/src/test/java/org/jenkinsci/plugins/DependencyTrack/ApiClientTest.java +++ b/src/test/java/org/jenkinsci/plugins/DependencyTrack/ApiClientTest.java @@ -38,6 +38,7 @@ import net.sf.json.JSONObject; import okhttp3.OkHttpClient; import org.assertj.core.api.InstanceOfAssertFactories; +import org.jenkinsci.plugins.DependencyTrack.ApiClient.ProjectData; import org.jenkinsci.plugins.DependencyTrack.model.Project; import org.jenkinsci.plugins.DependencyTrack.model.UploadResult; import org.junit.jupiter.api.AfterEach; @@ -365,11 +366,13 @@ void uploadTestWithUuid(@TempDir Path tmp, JenkinsRule r) throws IOException, In final var props = new ProjectProperties(); props.setParentId("parent-uuid"); + props.setIsLatest(true); ApiClient uut = createClient(); - assertThat(uut.upload("uuid-1", null, null, new FilePath(bom.toFile()), false, props)).isEqualTo(new UploadResult(true, "uuid-1")); + var data = new ProjectData("uuid-1", null, null, false, props); + assertThat(uut.upload(data, new FilePath(bom.toFile()))).isEqualTo(new UploadResult(true, "uuid-1")); - String expectedBody = "{\"bom\":\"PHRlc3QgLz4=\",\"project\":\"uuid-1\",\"parentUUID\":\"parent-uuid\"}"; + String expectedBody = "{\"bom\":\"PHRlc3QgLz4=\",\"project\":\"uuid-1\",\"parentUUID\":\"parent-uuid\",\"isLatestProjectVersion\":true}"; completionSignal.await(5, TimeUnit.SECONDS); assertThat(completionSignal.getCount()).isZero(); assertThat(requestBody.get()).isEqualTo(expectedBody); @@ -401,7 +404,8 @@ void uploadTestWithName(@TempDir Path tmp, JenkinsRule r) throws IOException, In props.setParentVersion("parent-version"); ApiClient uut = createClient(); - assertThat(uut.upload(null, "p1", "v1", new FilePath(bom.toFile()), false, props)).isEqualTo(new UploadResult(true)); + var data = new ProjectData(null, "p1", "v1", false, props); + assertThat(uut.upload(data, new FilePath(bom.toFile()))).isEqualTo(new UploadResult(true)); String expectedBody = "{\"bom\":\"PHRlc3QgLz4=\",\"projectName\":\"p1\",\"projectVersion\":\"v1\",\"autoCreate\":false,\"parentName\":\"parent-name\",\"parentVersion\":\"parent-version\"}"; completionSignal.await(5, TimeUnit.SECONDS); @@ -414,35 +418,36 @@ void uploadTestWithErrors(@TempDir Path tmp, JenkinsRule r) throws IOException { ApiClient uut; File bom = tmp.resolve("bom.xml").toFile(); bom.createNewFile(); + var data = new ProjectData(null, "p1", "v1", true, null); server = HttpServer.create().host("localhost").port(0).route(routes -> routes.put(ApiClient.BOM_URL, (request, response) -> response.status(HttpResponseStatus.BAD_REQUEST).send())).bindNow(); uut = createClient(); - assertThat(uut.upload(null, "p1", "v1", new FilePath(bom), true, null)).isEqualTo(new UploadResult(false)); + assertThat(uut.upload(data, new FilePath(bom))).isEqualTo(new UploadResult(false)); verify(logger).log(Messages.Builder_Payload_Invalid()); server.disposeNow(); server = HttpServer.create().host("localhost").port(0).route(routes -> routes.put(ApiClient.BOM_URL, (request, response) -> response.status(HttpResponseStatus.UNAUTHORIZED).send())).bindNow(); uut = createClient(); - assertThat(uut.upload(null, "p1", "v1", new FilePath(bom), true, null)).isEqualTo(new UploadResult(false)); + assertThat(uut.upload(data, new FilePath(bom))).isEqualTo(new UploadResult(false)); verify(logger).log(Messages.Builder_Unauthorized()); server.disposeNow(); server = HttpServer.create().host("localhost").port(0).route(routes -> routes.put(ApiClient.BOM_URL, (request, response) -> response.status(HttpResponseStatus.NOT_FOUND).send())).bindNow(); uut = createClient(); - assertThat(uut.upload(null, "p1", "v1", new FilePath(bom), true, null)).isEqualTo(new UploadResult(false)); + assertThat(uut.upload(data, new FilePath(bom))).isEqualTo(new UploadResult(false)); verify(logger).log(Messages.Builder_Project_NotFound()); server.disposeNow(); server = HttpServer.create().host("localhost").port(0).route(routes -> routes.put(ApiClient.BOM_URL, (request, response) -> response.status(HttpResponseStatus.GONE).send())).bindNow(); uut = createClient(); - assertThat(uut.upload(null, "p1", "v1", new FilePath(bom), true, null)).isEqualTo(new UploadResult(false)); + assertThat(uut.upload(data, new FilePath(bom))).isEqualTo(new UploadResult(false)); verify(logger).log(Messages.ApiClient_Error_Connection(HttpResponseStatus.GONE.code(), HttpResponseStatus.GONE.reasonPhrase())); server.disposeNow(); File mockFile = mock(File.class); when(mockFile.getPath()).thenReturn(tmp.toAbsolutePath().toString()); FilePath fileWithError = new FilePath(mockFile); - assertThat(uut.upload(null, "p1", "v1", fileWithError, true, null)).isEqualTo(new UploadResult(false)); + assertThat(uut.upload(data, fileWithError)).isEqualTo(new UploadResult(false)); verify(logger).log(startsWith(Messages.Builder_Error_Processing(tmp.toAbsolutePath().toString(), ""))); final var httpClient = mock(OkHttpClient.class); @@ -452,7 +457,7 @@ void uploadTestWithErrors(@TempDir Path tmp, JenkinsRule r) throws IOException { doThrow(new java.net.SocketTimeoutException("oops")) .when(call).execute(); - assertThatCode(() -> uutWithMock.upload(null, "p1", "v1", new FilePath(bom), true, null)) + assertThatCode(() -> uutWithMock.upload(data, new FilePath(bom))) .hasMessage(Messages.ApiClient_Error_Connection("", "")) .hasCauseInstanceOf(java.net.SocketTimeoutException.class); verify(httpClient, times(2)).newCall(any(okhttp3.Request.class)); @@ -542,7 +547,7 @@ void updateProjectPropertiesTest(JenkinsRule r) throws InterruptedException { assertThat(project.getParent()).hasFieldOrPropertyWithValue("uuid", props.getParentId()); assertThat(updatedProject.has("parentUuid")).isFalse(); - assertThatCode(() -> createClient().updateProjectProperties("uuid-unknown", new ProjectProperties())) + assertThatCode(() -> createClient().updateProjectProperties("uuid-unknown", props)) .hasMessage(Messages.ApiClient_Error_ProjectUpdate("uuid-unknown", HttpResponseStatus.NOT_FOUND.code(), HttpResponseStatus.NOT_FOUND.reasonPhrase())) .hasNoCause(); verify(logger).log(""); @@ -563,8 +568,10 @@ void updateProjectPropertiesTestWithStatus304(JenkinsRule r) throws InterruptedE .bindNow(); ApiClient uut = createClient(); + final var props = new ProjectProperties(); + props.setSwidTagId("swid"); - assertThatCode(() -> uut.updateProjectProperties("uuid-3", new ProjectProperties())).doesNotThrowAnyException(); + assertThatCode(() -> uut.updateProjectProperties("uuid-3", props)).doesNotThrowAnyException(); completionSignal.await(5, TimeUnit.SECONDS); assertThat(completionSignal.getCount()).isZero(); } @@ -574,11 +581,13 @@ void updateProjectPropertiesTestWithErrorsOnUpdate() throws IOException { final var httpClient = mock(OkHttpClient.class); final var call = mock(okhttp3.Call.class); final var uut = createClient(httpClient); + final var props = new ProjectProperties(); + props.setSwidTagId("swid"); when(httpClient.newCall(any(okhttp3.Request.class))).thenReturn(call); doThrow(new ConnectException("oops")) .when(call).execute(); - assertThatCode(() -> uut.updateProjectProperties("foo", new ProjectProperties())) + assertThatCode(() -> uut.updateProjectProperties("foo", props)) .hasMessage(Messages.ApiClient_Error_Connection("", "")) .hasCauseInstanceOf(ConnectException.class); verify(httpClient, times(2)).newCall(any(okhttp3.Request.class)); diff --git a/src/test/java/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisherTest.java b/src/test/java/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisherTest.java index 78c4cff..0b08d9c 100644 --- a/src/test/java/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisherTest.java +++ b/src/test/java/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisherTest.java @@ -49,7 +49,6 @@ import org.junit.jupiter.api.io.TempDir; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.junit.jupiter.WithJenkins; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; @@ -59,8 +58,8 @@ import static org.jenkinsci.plugins.DependencyTrack.model.Permissions.VIEW_POLICY_VIOLATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -157,7 +156,7 @@ void doNotThrowNPEinGetEffectiveApiKey(@TempDir Path tmpWork) throws IOException final DependencyTrackPublisher uut = new DependencyTrackPublisher(tmp.getName(), false, clientFactory); uut.setProjectId("uuid-1"); - when(client.upload(eq("uuid-1"), isNull(), isNull(), any(FilePath.class), eq(false), isNull())).thenThrow(new ApiClientException(Messages.ApiClient_Error_Connection("", ""))); + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))).thenThrow(new ApiClientException(Messages.ApiClient_Error_Connection("", ""))); assertThatCode(() -> uut.perform(build, workDir, env, launcher, listener)).isInstanceOf(ApiClientException.class).hasMessage(Messages.ApiClient_Error_Connection("", "")); } @@ -174,7 +173,7 @@ void testPerformAsync(@TempDir Path tmpWork) throws IOException { uut.setProjectProperties(props); uut.setUnstableTotalCritical(1); - when(client.upload(eq("uuid-1"), isNull(), isNull(), any(FilePath.class), eq(false), any(ProjectProperties.class))) + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))) .thenReturn(new UploadResult(true)) .thenReturn(new UploadResult(false)); @@ -199,13 +198,20 @@ void testPerformAsyncWithoutProjectId(@TempDir Path tmpWork) throws IOException uut.setDependencyTrackApiKey(apikeyId); uut.setAutoCreateProjects(Boolean.TRUE); - when(client.upload(isNull(), eq("name-1"), eq("my.value"), any(FilePath.class), eq(true), isNull())).thenReturn(new UploadResult(true, "token-1")); + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))).thenReturn(new UploadResult(true, "token-1")); assertThatCode(() -> uut.perform(build, workDir, env, launcher, listener)).doesNotThrowAnyException(); verify(client, never()).lookupProject(anyString(), anyString()); verify(client, never()).getFindings(anyString()); verify(client, never()).getTeamPermissions(); verify(client, never()).getViolations(anyString()); + verify(client).upload(assertArg(data -> { + assertThat(data.id()).isNull(); + assertThat(data.name()).isEqualTo("name-1"); + assertThat(data.version()).isEqualTo("my.value"); + assertThat(data.autoCreate()).isTrue(); + assertThat(data.properties()).isNull(); + }), any(FilePath.class)); } @Test @@ -218,7 +224,7 @@ void testPerformSync(@TempDir Path tmpWork) throws IOException { uut.setDependencyTrackApiKey(apikeyId); uut.setUnstableTotalCritical(1); - when(client.upload(eq("uuid-1"), isNull(), isNull(), any(FilePath.class), eq(false), isNull())).thenReturn(new UploadResult(true, "token-1")); + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))).thenReturn(new UploadResult(true, "token-1")); when(client.isTokenBeingProcessed("token-1")).thenReturn(Boolean.TRUE).thenReturn(Boolean.FALSE); when(client.getFindings("uuid-1")).thenReturn(List.of()); when(client.getTeamPermissions()).thenReturn(Team.builder().name("test-team").permissions(Set.of()).build()); @@ -239,6 +245,13 @@ void testPerformSync(@TempDir Path tmpWork) throws IOException { verify(client).getFindings("uuid-1"); verify(client).getTeamPermissions(); verify(buildWithResultAction, times(2)).getAction(ResultAction.class); + verify(client).upload(assertArg(data -> { + assertThat(data.id()).isEqualTo("uuid-1"); + assertThat(data.name()).isNull(); + assertThat(data.version()).isNull(); + assertThat(data.autoCreate()).isFalse(); + assertThat(data.properties()).isNull(); + }), any(FilePath.class)); } @Test @@ -250,7 +263,7 @@ void testPerformSyncNoThresholds(@TempDir Path tmpWork) throws IOException { uut.setProjectId("uuid-1"); uut.setDependencyTrackApiKey(apikeyId); - when(client.upload(eq("uuid-1"), isNull(), isNull(), any(FilePath.class), eq(false), isNull())).thenReturn(new UploadResult(true, "token-1")); + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))).thenReturn(new UploadResult(true, "token-1")); when(client.isTokenBeingProcessed("token-1")).thenReturn(Boolean.TRUE).thenReturn(Boolean.FALSE); when(client.getFindings("uuid-1")).thenReturn(List.of()); when(client.getTeamPermissions()).thenReturn(Team.builder().name("test-team").permissions(Set.of()).build()); @@ -276,7 +289,7 @@ void testPerformSyncNoThresholdsWarnOnViolationWarn(@TempDir Path tmpWork) throw uut.setDependencyTrackApiKey(apikeyId); uut.setWarnOnViolationWarn(true); - when(client.upload(eq("uuid-1"), isNull(), isNull(), any(FilePath.class), eq(false), isNull())).thenReturn(new UploadResult(true, "token-1")); + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))).thenReturn(new UploadResult(true, "token-1")); when(client.isTokenBeingProcessed("token-1")).thenReturn(Boolean.FALSE); when(client.getFindings("uuid-1")).thenReturn(List.of()); when(client.getTeamPermissions()).thenReturn(Team.builder().name("test-team").permissions(Set.of(VIEW_POLICY_VIOLATION.toString())).build()); @@ -300,7 +313,7 @@ void testPerformSyncNoThresholdsFailOnViolationFail(@TempDir Path tmpWork) throw uut.setDependencyTrackApiKey(apikeyId); uut.setFailOnViolationFail(true); - when(client.upload(eq("uuid-1"), isNull(), isNull(), any(FilePath.class), eq(false), isNull())).thenReturn(new UploadResult(true, "token-1")); + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))).thenReturn(new UploadResult(true, "token-1")); when(client.isTokenBeingProcessed("token-1")).thenReturn(Boolean.FALSE); when(client.getFindings("uuid-1")).thenReturn(List.of()); when(client.getTeamPermissions()).thenReturn(Team.builder().name("test-team").permissions(Set.of(VIEW_POLICY_VIOLATION.toString())).build()); @@ -322,6 +335,7 @@ void testPerformSyncWithoutProjectId(@TempDir Path tmpWork) throws IOException { props.setParentId("parentId. ${my.var}"); props.setParentName("parentName. ${my.var}"); props.setParentVersion("parentVersion. ${my.var}"); + props.setIsLatest(true); DependencyTrackPublisher uut = new DependencyTrackPublisher(tmp.getName(), true, clientFactory); uut.setProjectName("name-1"); uut.setProjectVersion("version-1"); @@ -330,7 +344,7 @@ void testPerformSyncWithoutProjectId(@TempDir Path tmpWork) throws IOException { uut.setProjectProperties(props); final var team = Team.builder().name("test-team").permissions(Set.of(VIEW_POLICY_VIOLATION.toString())).build(); - when(client.upload(isNull(), eq("name-1"), eq("version-1"), any(FilePath.class), eq(true), any(ProjectProperties.class))).thenReturn(new UploadResult(true, "token-1")); + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))).thenReturn(new UploadResult(true, "token-1")); when(client.isTokenBeingProcessed("token-1")).thenReturn(Boolean.TRUE).thenReturn(Boolean.FALSE); when(client.getFindings("uuid-1")).thenReturn(List.of()); when(client.getTeamPermissions()).thenReturn(team); @@ -343,15 +357,16 @@ void testPerformSyncWithoutProjectId(@TempDir Path tmpWork) throws IOException { verify(client).getFindings("uuid-1"); verify(client).getTeamPermissions(); verify(client).getViolations("uuid-1"); - var propsCaptor = ArgumentCaptor.forClass(ProjectProperties.class); - verify(client).updateProjectProperties(eq("uuid-1"), propsCaptor.capture()); - assertThat(propsCaptor.getValue().getDescription()).isEqualTo("description. my.value"); - assertThat(propsCaptor.getValue().getTags()).containsExactlyInAnyOrder("tag1", "my.value"); - assertThat(propsCaptor.getValue().getSwidTagId()).isEqualTo("swidTagId. my.value"); - assertThat(propsCaptor.getValue().getGroup()).isEqualTo("group. my.value"); - assertThat(propsCaptor.getValue().getParentId()).isEqualTo("parentId. my.value"); - assertThat(propsCaptor.getValue().getParentName()).isEqualTo("parentName. my.value"); - assertThat(propsCaptor.getValue().getParentVersion()).isEqualTo("parentVersion. my.value"); + verify(client).updateProjectProperties(eq("uuid-1"), assertArg(actualProps -> { + assertThat(actualProps.getDescription()).isEqualTo("description. my.value"); + assertThat(actualProps.getTags()).containsExactlyInAnyOrder("tag1", "my.value"); + assertThat(actualProps.getSwidTagId()).isEqualTo("swidTagId. my.value"); + assertThat(actualProps.getGroup()).isEqualTo("group. my.value"); + assertThat(actualProps.getParentId()).isEqualTo("parentId. my.value"); + assertThat(actualProps.getParentName()).isEqualTo("parentName. my.value"); + assertThat(actualProps.getParentVersion()).isEqualTo("parentVersion. my.value"); + assertThat(actualProps.getIsLatest()).isTrue(); + })); verify(client).lookupProject("name-1", "version-1"); } @@ -376,8 +391,7 @@ void testUseOfOverridenProperties(@TempDir Path tmpWork) throws IOException { uut.setDependencyTrackConnectionTimeout(1); uut.setDependencyTrackReadTimeout(1); - when(client.upload(eq("uuid-1"), isNull(), isNull(), any(FilePath.class), eq(true), isNull())) - .thenReturn(new UploadResult(false)); + when(client.upload(any(ApiClient.ProjectData.class), any(FilePath.class))).thenReturn(new UploadResult(false)); assertThatCode(() -> uut.perform(build, workDir, env, launcher, listener)).isInstanceOf(AbortException.class).hasMessage(Messages.Builder_Upload_Failed()); } diff --git a/src/test/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImplTest.java b/src/test/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImplTest.java index 5415caf..2b7c9a2 100644 --- a/src/test/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImplTest.java +++ b/src/test/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImplTest.java @@ -143,7 +143,7 @@ void doTestConnectionTest() throws IOException { assertThat(uut.doTestConnectionGlobal("http:///url.tld", credentialsid, false, null)) .hasFieldOrPropertyWithValue("kind", FormValidation.Kind.ERROR) - .hasMessage(Messages.Publisher_ConnectionTest_VersionWarning("3.8.0", "4.9.0")) + .hasMessage(Messages.Publisher_ConnectionTest_VersionWarning("3.8.0", "4.12.0")) .hasNoCause(); assertThat(uut.doTestConnectionGlobal("http:///url.tld/", credentialsid, false, null)) @@ -196,8 +196,8 @@ void doTestConnectionTestWithEmptyArgs() throws IOException { assertThat(logger).isInstanceOf(ConsoleLogger.class); return client; }; - when(client.testConnection()).thenReturn("Dependency-Track v4.9.0"); - when(client.getVersion()).thenReturn(new VersionNumber("4.9.0")); + when(client.testConnection()).thenReturn("Dependency-Track v4.12.0"); + when(client.getVersion()).thenReturn(new VersionNumber("4.12.0")); when(client.getTeamPermissions()).thenReturn(Team.builder().name("my-team").permissions(requiredPermissions).build()); try (ACLContext ignored = ACL.as(User.getOrCreateByIdOrFullName(ACL.SYSTEM_USERNAME))) { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), new StringCredentialsImpl(CredentialsScope.GLOBAL, credentialsid, "test", Secret.fromString(apikey))); @@ -226,9 +226,9 @@ void doTestConnectionTestWithTeamPermissions() throws IOException { assertThat(logger).isInstanceOf(ConsoleLogger.class); return client; }; - final var poweredBy = "Dependency-Track v4.9.0"; + final var poweredBy = "Dependency-Track v4.12.0"; when(client.testConnection()).thenReturn(poweredBy); - when(client.getVersion()).thenReturn(new VersionNumber("4.9.0")); + when(client.getVersion()).thenReturn(new VersionNumber("4.12.0")); when(client.getTeamPermissions()).thenReturn(team); try (ACLContext ignored = ACL.as(User.getOrCreateByIdOrFullName(ACL.SYSTEM_USERNAME))) { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), new StringCredentialsImpl(CredentialsScope.GLOBAL, credentialsid, "test", Secret.fromString(apikey))); @@ -266,9 +266,9 @@ void doTestConnectionTestWithMissingRequiredPermission() throws IOException { assertThat(logger).isInstanceOf(ConsoleLogger.class); return client; }; - final var poweredBy = "Dependency-Track v4.9.0"; + final var poweredBy = "Dependency-Track v4.12.0"; when(client.testConnection()).thenReturn(poweredBy); - when(client.getVersion()).thenReturn(new VersionNumber("4.9.0")); + when(client.getVersion()).thenReturn(new VersionNumber("4.12.0")); when(client.getTeamPermissions()).thenReturn(team); try (ACLContext ignored = ACL.as(User.getOrCreateByIdOrFullName(ACL.SYSTEM_USERNAME))) { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), new StringCredentialsImpl(CredentialsScope.GLOBAL, credentialsid, "test", Secret.fromString(apikey))); @@ -306,9 +306,9 @@ void doTestConnectionTestWithUnneededPermission() throws IOException { assertThat(logger).isInstanceOf(ConsoleLogger.class); return client; }; - final var poweredBy = "Dependency-Track v4.9.0"; + final var poweredBy = "Dependency-Track v4.12.0"; when(client.testConnection()).thenReturn(poweredBy); - when(client.getVersion()).thenReturn(new VersionNumber("4.9.0")); + when(client.getVersion()).thenReturn(new VersionNumber("4.12.0")); when(client.getTeamPermissions()).thenReturn(team); try (ACLContext ignored = ACL.as(User.getOrCreateByIdOrFullName(ACL.SYSTEM_USERNAME))) { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), new StringCredentialsImpl(CredentialsScope.GLOBAL, credentialsid, "test", Secret.fromString(apikey)));