From e41244d520011253507b992b508710e966387b11 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Tue, 12 May 2026 18:29:53 +0200 Subject: [PATCH 1/5] test(integrations): add tests for defectdojo.groupBy per-project property Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com> --- .../defectdojo/DefectDojoUploaderTest.java | 24 +++ .../tasks/DefectDojoUploadTaskTest.java | 178 ++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/src/test/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploaderTest.java b/src/test/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploaderTest.java index ace125478a..c1104dfd0a 100644 --- a/src/test/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploaderTest.java +++ b/src/test/java/org/dependencytrack/integrations/defectdojo/DefectDojoUploaderTest.java @@ -68,4 +68,28 @@ void testIntegrationDisabledCases() { Assertions.assertFalse(extension.isProjectConfigured(project)); } + @Test + void testGetGroupByReturnsNullWhenNotConfigured() { + Project project = qm.createProject("ACME Example", null, "1.0", null, null, null, true, false); + DefectDojoUploader extension = new DefectDojoUploader(); + extension.setQueryManager(qm); + Assertions.assertNull(extension.getGroupBy(project)); + } + + @Test + void testGetGroupByReturnsValueWhenConfigured() { + Project project = qm.createProject("ACME Example", null, "1.0", null, null, null, true, false); + qm.createProjectProperty( + project, + DEFECTDOJO_ENABLED.getGroupName(), + "defectdojo.groupBy", + "component_name", + IConfigProperty.PropertyType.STRING, + null + ); + DefectDojoUploader extension = new DefectDojoUploader(); + extension.setQueryManager(qm); + Assertions.assertEquals("component_name", extension.getGroupBy(project)); + } + } diff --git a/src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java b/src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java index c5c60c62a0..021041568d 100644 --- a/src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/DefectDojoUploadTaskTest.java @@ -909,6 +909,184 @@ void testUploadWithReimportAndNoExistingTest() { """, true, false)))); } + @Test + void testUploadWithGroupBy() { + qm.createConfigProperty( + DEFECTDOJO_ENABLED.getGroupName(), + DEFECTDOJO_ENABLED.getPropertyName(), + "true", + DEFECTDOJO_ENABLED.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_URL.getGroupName(), + DEFECTDOJO_URL.getPropertyName(), + wmRuntimeInfo.getHttpBaseUrl(), + DEFECTDOJO_URL.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_API_KEY.getGroupName(), + DEFECTDOJO_API_KEY.getPropertyName(), + "dojoApiKey", + DEFECTDOJO_API_KEY.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_REIMPORT_ENABLED.getGroupName(), + DEFECTDOJO_REIMPORT_ENABLED.getPropertyName(), + DEFECTDOJO_REIMPORT_ENABLED.getDefaultPropertyValue(), + DEFECTDOJO_REIMPORT_ENABLED.getPropertyType(), + null + ); + + stubFor(post(urlPathEqualTo("/api/v2/import-scan/")) + .willReturn(aResponse() + .withStatus(201))); + + final var project = new Project(); + project.setName("acme-app"); + project.setVersion("1.0.0"); + qm.persist(project); + + final var component = new Component(); + component.setProject(project); + component.setName("acme-lib"); + component.setVersion("1.2.3"); + qm.persist(component); + + qm.createProjectProperty(project, "integrations", "defectdojo.engagementId", + "666", IConfigProperty.PropertyType.STRING, null); + qm.createProjectProperty(project, "integrations", "defectdojo.groupBy", + "component_name", IConfigProperty.PropertyType.STRING, null); + + new DefectDojoUploadTask().inform(new DefectDojoUploadEventAbstract()); + + verify(postRequestedFor(urlPathEqualTo("/api/v2/import-scan/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token dojoApiKey")) + .withAnyRequestBodyPart(aMultipart() + .withName("engagement") + .withBody(equalTo("666"))) + .withAnyRequestBodyPart(aMultipart() + .withName("group_by") + .withBody(equalTo("component_name")))); + } + + @Test + void testUploadWithReimportAndGroupBy() { + qm.createConfigProperty( + DEFECTDOJO_ENABLED.getGroupName(), + DEFECTDOJO_ENABLED.getPropertyName(), + "true", + DEFECTDOJO_ENABLED.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_URL.getGroupName(), + DEFECTDOJO_URL.getPropertyName(), + wmRuntimeInfo.getHttpBaseUrl(), + DEFECTDOJO_URL.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_API_KEY.getGroupName(), + DEFECTDOJO_API_KEY.getPropertyName(), + "dojoApiKey", + DEFECTDOJO_API_KEY.getPropertyType(), + null + ); + qm.createConfigProperty( + DEFECTDOJO_REIMPORT_ENABLED.getGroupName(), + DEFECTDOJO_REIMPORT_ENABLED.getPropertyName(), + "false", + DEFECTDOJO_REIMPORT_ENABLED.getPropertyType(), + null + ); + + stubFor(get(urlPathEqualTo("/api/v2/tests/")) + .withQueryParam("engagement", equalTo("666")) + .withQueryParam("limit", equalTo("100")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token dojoApiKey")) + .willReturn(aResponse() + .withStatus(200) + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .withBody(""" + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 1, + "tags": [], + "test_type_name": "Dependency Track Finding Packaging Format (FPF) Export", + "finding_groups": [], + "scan_type": "Dependency Track Finding Packaging Format (FPF) Export", + "title": null, + "description": null, + "target_start": "2023-04-29T00:00:00Z", + "target_end": "2023-04-29T21:39:21.513481Z", + "estimated_time": null, + "actual_time": null, + "percent_complete": 100, + "updated": "2023-04-29T21:39:21.617857Z", + "created": "2023-04-29T21:39:21.516216Z", + "version": "", + "build_id": "", + "commit_hash": "", + "branch_tag": "", + "engagement": 666, + "lead": 1, + "test_type": 63, + "environment": 7, + "api_scan_configuration": null, + "notes": [], + "files": [] + } + ], + "prefetch": {} + } + """))); + + stubFor(post(urlPathEqualTo("/api/v2/reimport-scan/")) + .willReturn(aResponse() + .withStatus(201))); + + final var project = new Project(); + project.setName("acme-app"); + project.setVersion("1.0.0"); + qm.persist(project); + + final var component = new Component(); + component.setProject(project); + component.setName("acme-lib"); + component.setVersion("1.2.3"); + qm.persist(component); + + qm.createProjectProperty(project, "integrations", "defectdojo.engagementId", + "666", IConfigProperty.PropertyType.STRING, null); + qm.createProjectProperty(project, "integrations", "defectdojo.reimport", + "true", IConfigProperty.PropertyType.BOOLEAN, null); + qm.createProjectProperty(project, "integrations", "defectdojo.groupBy", + "component_name+component_version", IConfigProperty.PropertyType.STRING, null); + + new DefectDojoUploadTask().inform(new DefectDojoUploadEventAbstract()); + + verify(1, getRequestedFor(urlPathEqualTo("/api/v2/tests/"))); + + verify(postRequestedFor(urlPathEqualTo("/api/v2/reimport-scan/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token dojoApiKey")) + .withAnyRequestBodyPart(aMultipart() + .withName("engagement") + .withBody(equalTo("666"))) + .withAnyRequestBodyPart(aMultipart() + .withName("test") + .withBody(equalTo("1"))) + .withAnyRequestBodyPart(aMultipart() + .withName("group_by") + .withBody(equalTo("component_name+component_version")))); + } + /** * Un-ignore this test to test the integration against a local DefectDojo deployment. *
From 0ed88a43aeefcf827f6961f38f31f11e17f0caef Mon Sep 17 00:00:00 2001
From: webdevred <148627186+webdevred@users.noreply.github.com>
Date: Tue, 12 May 2026 18:30:02 +0200
Subject: [PATCH 2/5] feat(integrations): add defectdojo.groupBy per-project
property
When set, forwards the value as group_by in the DefectDojo import-scan
and reimport-scan multipart form requests, allowing findings to be
grouped into Finding Groups on import.
When not set, behavior is unchanged (backwards compatible).
Closes #6061
Signed-off-by: webdevred <148627186+webdevred@users.noreply.github.com>
---
.../defectdojo/DefectDojoClient.java | 14 ++++++++++----
.../defectdojo/DefectDojoUploader.java | 19 +++++++++++++++----
2 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoClient.java b/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoClient.java
index 340eeb00b1..dc5f972939 100644
--- a/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoClient.java
+++ b/src/main/java/org/dependencytrack/integrations/defectdojo/DefectDojoClient.java
@@ -55,7 +55,7 @@ public DefectDojoClient(final DefectDojoUploader uploader, final URL baseURL) {
this.baseURL = baseURL;
}
- public void uploadDependencyTrackFindings(final String token, final String engagementId, final InputStream findingsJson, final Boolean verifyFindings, final String testTitle) {
+ public void uploadDependencyTrackFindings(final String token, final String engagementId, final InputStream findingsJson, final Boolean verifyFindings, final String testTitle, final String groupBy) {
LOGGER.debug("Uploading Dependency-Track findings to DefectDojo");
HttpPost request = new HttpPost(baseURL + "/api/v2/import-scan/");
InputStreamBody inputStreamBody = new InputStreamBody(findingsJson, ContentType.APPLICATION_OCTET_STREAM, "findings.json");
@@ -72,9 +72,12 @@ public void uploadDependencyTrackFindings(final String token, final String engag
.addPart("close_old_findings", new StringBody("true", ContentType.MULTIPART_FORM_DATA))
.addPart("push_to_jira", new StringBody("false", ContentType.MULTIPART_FORM_DATA))
.addPart("scan_date", new StringBody(DATE_FORMAT.format(new Date()), ContentType.MULTIPART_FORM_DATA));
- if(testTitle != null) {
+ if (testTitle != null) {
builder.addPart("test_title", new StringBody(testTitle, ContentType.MULTIPART_FORM_DATA));
}
+ if (groupBy != null) {
+ builder.addPart("group_by", new StringBody(groupBy, ContentType.MULTIPART_FORM_DATA));
+ }
request.setEntity(builder.build());
try (CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED) {
@@ -164,7 +167,7 @@ public ArrayList