diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index fdb8fa54..131c62ef 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -48,7 +48,7 @@ jobs: EXHORT_PYTHON3_PATH: "${{steps.python-location.outputs.python-bin-location}}/python3" EXHORT_PIP3_PATH: "${{steps.python-location.outputs.python-bin-location}}/pip3" run: | - mvn verify -Pcov -B -ff + mvn verify -Pits,cov -B -ff - name: Report test summary if: ${{ matrix.java == env.MAIN_JAVA_VER && always() }} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..346d645f --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/pom.xml b/pom.xml index 3746522c..9665a7b4 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,6 @@ 3.0.1 3.4.0 3.1.1 - 3.2.2 3.3.0 3.4.1 3.0.0 @@ -54,6 +53,7 @@ 1.13.2 1.1.2 2.15.0 + 3.2.5 @@ -269,10 +269,6 @@ maven-install-plugin ${maven-install-plugin.version} - - maven-invoker-plugin - ${maven-invoker-plugin.version} - maven-jar-plugin ${maven-jar-plugin.version} @@ -434,6 +430,11 @@ limitations under the License.]]> false + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + @@ -486,6 +487,9 @@ limitations under the License.]]> maven-surefire-plugin ${maven-surefire-plugin.version} + + IntegrationTest + plain false @@ -663,7 +667,6 @@ limitations under the License.]]> - @@ -714,24 +717,27 @@ limitations under the License.]]> its - - - maven-invoker-plugin + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + org.junit.jupiter + junit-jupiter-engine + 5.9.1 + + - ${project.build.directory}/it - ${project.build.directory}/local-it-repo - - test - - src/it/settings.xml - true + + IntegrationTest + - install - run + integration-test + verify @@ -789,9 +795,6 @@ limitations under the License.]]> junit-platform-maven-plugin ${junit-platform-maven-plugin.version} - - - diff --git a/src/it/simple-modular/pom.xml b/src/it/simple-modular/pom.xml deleted file mode 100644 index 80f5b366..00000000 --- a/src/it/simple-modular/pom.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - 4.0.0 - - com.redhat.exhort.integration.tests - exhort-java-api-simple-modular-it - 0.0.1 - exhort-java-api-simple-modular-it - - - 11 - UTF-8 - true - true - - - - - com.redhat.exhort - exhort-java-api - @project.version@ - - - com.fasterxml.jackson.core - jackson-core - 2.15.0 - test - - - com.fasterxml.jackson.core - jackson-databind - 2.15.1 - test - - - org.assertj - assertj-core - 3.24.2 - - - org.junit.jupiter - junit-jupiter-api - 5.9.1 - test - - - org.mockito - mockito-core - 5.3.1 - test - - - - - test - - - - maven-compiler-plugin - 3.11.0 - - - de.sormuras.junit - junit-platform-maven-plugin - 1.1.7 - true - - JAVA - - org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores - - - - - - - diff --git a/src/it/simple-modular/src/test/java/module-info.test b/src/it/simple-modular/src/test/java/module-info.test deleted file mode 100644 index 166218d4..00000000 --- a/src/it/simple-modular/src/test/java/module-info.test +++ /dev/null @@ -1,16 +0,0 @@ ---add-modules - jdk.attach,org.assertj.core,org.mockito,net.bytebuddy,net.bytebuddy.agent - ---add-reads - com.redhat.exhort.simple.modular.it=com.fasterxml.jackson.databind ---add-reads - com.redhat.exhort.simple.modular.it=org.assertj.core ---add-reads - com.redhat.exhort.simple.modular.it=org.mockito ---add-reads - com.redhat.exhort.simple.modular.it=java.net.http - ---add-opens - com.redhat.exhort.simple.modular.it/simple.modular=org.junit.platform.commons ---add-opens - com.redhat.exhort/com.redhat.exhort.impl=com.redhat.exhort.simple.modular.it diff --git a/src/it/simple-modular/src/test/java/simple/modular/Simple_Integration_Test.java b/src/it/simple-modular/src/test/java/simple/modular/Simple_Integration_Test.java deleted file mode 100644 index 27767779..00000000 --- a/src/it/simple-modular/src/test/java/simple/modular/Simple_Integration_Test.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright © 2023 Red Hat, Inc. - * - * Licensed 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 simple.modular; - -import static org.assertj.core.api.Assertions.as; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.redhat.exhort.api.AnalysisReport; -import com.redhat.exhort.impl.ExhortApi; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import java.net.http.HttpClient; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.concurrent.CompletableFuture; - -class Simple_Integration_Test { - static boolean useRealAPI = true; - ExhortApi exhortApi; - HttpClient mockHttpClient; - - ObjectMapper mapper; - - @BeforeAll - static void prepare() { - var useRealApi = System.getenv("EXHORT_ITS_USE_REAL_API"); - Simple_Integration_Test.useRealAPI = Boolean.parseBoolean(useRealApi); - } - @BeforeEach - void initialize() throws Exception { - mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - if (Simple_Integration_Test.useRealAPI) { - exhortApi = new ExhortApi(); - } else { - // mock a http client instance - mockHttpClient = mock(HttpClient.class); - // use reflection to make the constructor that takes a http client accessible - var sutConstructor = ExhortApi.class.getDeclaredConstructor(HttpClient.class); - sutConstructor.setAccessible(true); - exhortApi = sutConstructor.newInstance(mockHttpClient); - } - } - - @Test - void test_stack_analysis_html_report() throws Exception { - // load the pre-configured expected html response - var expectedHtmlAnalysis = Files.readAllBytes(Paths.get("src/test/resources/it_poms/analysis-report.html")); - if (!Simple_Integration_Test.useRealAPI) { - // mock a http response object and stub it to return the expected html report as a body - var mockHtmlResponse = mock(HttpResponse.class); - when(mockHtmlResponse.body()).thenReturn(expectedHtmlAnalysis); - when(mockHtmlResponse.statusCode()).thenReturn(200); - // stub the mocked http client to return the mocked http response for requests accepting text/html - when(mockHttpClient.sendAsync( - argThat(r -> r.headers().firstValue("Accept").get().equals("text/html")), any()) - ).thenReturn(CompletableFuture.completedFuture(mockHtmlResponse)); - } - - // get the html report from the api - var htmlAnalysis = exhortApi.stackAnalysisHtml("src/test/resources/it_poms/pom.xml").get(); - assertThat(htmlAnalysis).isEqualTo(expectedHtmlAnalysis); - } - - @Test - void test_stack_analysis_mixed_report() throws Exception { - // load the pre-configured expected html and json responses - var expectedHtmlAnalysis = Files.readAllBytes(Paths.get("src/test/resources/it_poms/analysis-report.html")); - var expectedAnalysisJson = Files.readString(Paths.get("src/test/resources/it_poms/analysis-report.json")); - // deserialize the json expected response - var expectedAnalysis = mapper.readValue(expectedAnalysisJson, AnalysisReport.class); - - var expectedMixedAnalysis = Files.readAllBytes(Paths.get("src/test/resources/it_poms/analysis-report.mixed")); - if (!Simple_Integration_Test.useRealAPI) { - // mock a http response object and stub it to return the expected html report as a body - var mockMixedResponse = mock(HttpResponse.class); - when(mockMixedResponse.body()).thenReturn(expectedMixedAnalysis); - when(mockMixedResponse.statusCode()).thenReturn(200); - // stub the mocked http client to return the mocked http response for requests accepting text/html - when(mockHttpClient.sendAsync( - argThat(r -> r.headers().firstValue("Accept").get().equals("multipart/mixed")), any()) - ).thenReturn(CompletableFuture.completedFuture(mockMixedResponse)); - } - - // get the html report from the api - var mixedAnalysis = exhortApi.stackAnalysisMixed("src/test/resources/it_poms/pom.xml").get(); - assertThat(new String(mixedAnalysis.html).trim()).isEqualTo(new String(expectedHtmlAnalysis).trim()); - assertThat(mixedAnalysis.json).isEqualTo(expectedAnalysis); - } - - @Test - void test_stack_analysis_report() throws Exception { - // load the pre-configured expected json response - var expectedAnalysisJson = Files.readString(Paths.get("src/test/resources/it_poms/analysis-report.json")); - // deserialize the expected response - var expectedAnalysis = mapper.readValue(expectedAnalysisJson, AnalysisReport.class); - if (!Simple_Integration_Test.useRealAPI) { - // mock a http response object and stub it to return the expected json report as a body - var mockJsonResponse = mock(HttpResponse.class); - when(mockJsonResponse.body()).thenReturn(expectedAnalysisJson); - when(mockJsonResponse.statusCode()).thenReturn(200); - // stub the mocked http client to return the mocked http response for requests accepting json application - when(mockHttpClient.sendAsync( - argThat(r -> r.headers().firstValue("Accept").get().equals("application/json")), any()) - ).thenReturn(CompletableFuture.completedFuture(mockJsonResponse)); - } - - // get the analysis report object from the api - var analysisReport = exhortApi.stackAnalysis("src/test/resources/it_poms/pom.xml").get(); - assertThat(analysisReport).isEqualTo(expectedAnalysis); - } - - @Test - void test_component_analysis_report() throws Exception { - // load the pre-configured expected json response - var expectedAnalysisJson = Files.readString(Paths.get("src/test/resources/it_poms/analysis-report.json")); - // deserialize the expected response - var expectedAnalysis = mapper.readValue(expectedAnalysisJson, AnalysisReport.class); - if (!Simple_Integration_Test.useRealAPI) { - // mock a http response object and stub it to return the expected json report as a body - var mockJsonResponse = mock(HttpResponse.class); - when(mockJsonResponse.body()).thenReturn(expectedAnalysisJson); - when(mockJsonResponse.statusCode()).thenReturn(200); - // stub the mocked http client to return the mocked http response for requests accepting json application - when(mockHttpClient.sendAsync( - argThat(r -> r.headers().firstValue("Accept").get().equals("application/json")), any()) - ).thenReturn(CompletableFuture.completedFuture(mockJsonResponse)); - } - // get the analysis report object from the api - var pomContent = Files.readAllBytes(Paths.get("src/test/resources/it_poms/pom.xml")); - var analysisReport = exhortApi.componentAnalysis("pom.xml", pomContent).get(); - assertThat(analysisReport).isEqualTo(expectedAnalysis); - } -} diff --git a/src/it/simple-modular/src/test/resources/it_poms/analysis-report.html b/src/it/simple-modular/src/test/resources/it_poms/analysis-report.html deleted file mode 100644 index 14a74c61..00000000 --- a/src/it/simple-modular/src/test/resources/it_poms/analysis-report.html +++ /dev/null @@ -1,593 +0,0 @@ - - - Combined Shape - - - - - - - - - - - - - - - - - - - - - - - - - - - Dependency Analysis - - - -
-
- - - - - -   Security Issues - -
-
-
-
-

Below is a list of dependencies affected with CVE, as well as vulnerability only found using Snyk's vulnerability database.

-
-
-

Dependencies with security issues in your stack.

-

Dependencies with high common vulnerabilities and exposures (CVE) score.

-

- - - - - - Total Vulnerabilities: 7 -

-

- - - - - - Vulnerable Dependencies: 1 -

-
-
-
-
- -
-
Commonly Known Vulnerabilities
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#Dependencies# Direct# TransitiveHighest CVSSHighest SeverityRed Hat remediation available
-
-

Details of the dependency: - log4j:log4j -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SeverityExploit MaturityDescriptionCVSSCVERemediation
- - CRITICAL - - - Proof of concept codeDeserialization of Untrusted Data -
-
9.8/10
-
-
-
-
- -
- CVE-2019-17571 - - - SNYK-JAVA-LOG4J-572732 - -
- - HIGH - - - No known exploitSQL Injection -
-
8.1/10
-
-
-
-
- -
- CVE-2022-23305 - - - SNYK-JAVA-LOG4J-2342645 - -
- - HIGH - - - No known exploitDeserialization of Untrusted Data -
-
8.1/10
-
-
-
-
- -
- CVE-2022-23307 - - - SNYK-JAVA-LOG4J-2342646 - -
- - HIGH - - - No known exploitDeserialization of Untrusted Data -
-
8.1/10
-
-
-
-
- -
- CVE-2022-23302 - - - SNYK-JAVA-LOG4J-2342647 - -
- - MEDIUM - - - Proof of concept codeArbitrary Code Execution -
-
6.6/10
-
-
-
-
- -
- CVE-2021-4104 - - - SNYK-JAVA-LOG4J-2316893 - -
- - MEDIUM - - - No known exploitDenial of Service (DoS) -
-
5.9/10
-
-
-
-
- -
- CVE-2023-26464 - - - SNYK-JAVA-LOG4J-3358774 - -
- - LOW - - - No known exploitMan-in-the-Middle (MitM) -
-
3.7/10
-
-
-
-
- -
- CVE-2020-9488 - - - SNYK-JAVA-LOG4J-1300176 - -
-
- -
- - - - - - - - - - - - - - -
DependenciesSeverityExploit MaturityDescriptionCVSSCVERemediation
-
-
-
-
- - - - - - - - - - \ No newline at end of file diff --git a/src/it/simple-modular/src/test/resources/it_poms/analysis-report.json b/src/it/simple-modular/src/test/resources/it_poms/analysis-report.json deleted file mode 100644 index 208c07ae..00000000 --- a/src/it/simple-modular/src/test/resources/it_poms/analysis-report.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "summary": { - "dependencies": { - "scanned": 1, - "transitive": 0 - }, - "vulnerabilities": { - "total": 7, - "direct": 1, - "critical": 1, - "high": 3, - "medium": 2, - "low": 1 - }, - "providerStatuses": [ - { - "ok": true, - "provider": "snyk", - "status": 200, - "message": "OK" - } - ] - }, - "dependencies": [ - { - "ref": "pkg:maven/log4j/log4j@1.2.17", - "highestVulnerability": { - "id": "SNYK-JAVA-LOG4J-1300176", - "title": "Man-in-the-Middle (MitM)", - "source": "snyk", - "cvss": { - "attackVector": "Network", - "attackComplexity": "High", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "Low", - "integrityImpact": "None", - "availabilityImpact": "None", - "exploitCodeMaturity": null, - "remediationLevel": null, - "reportConfidence": null, - "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N" - }, - "cvssScore": 3.7, - "severity": "LOW", - "cves": [ - "CVE-2020-9488" - ], - "unique": false - }, - "issues": [ - { - "id": "SNYK-JAVA-LOG4J-572732", - "title": "Deserialization of Untrusted Data", - "source": "snyk", - "cvss": { - "attackVector": "Network", - "attackComplexity": "Low", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "High", - "availabilityImpact": "High", - "exploitCodeMaturity": "Proof of concept code", - "remediationLevel": null, - "reportConfidence": null, - "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:P" - }, - "cvssScore": 9.8, - "severity": "CRITICAL", - "cves": [ - "CVE-2019-17571" - ], - "unique": false - }, - { - "id": "SNYK-JAVA-LOG4J-2342645", - "title": "SQL Injection", - "source": "snyk", - "cvss": { - "attackVector": "Network", - "attackComplexity": "High", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "High", - "availabilityImpact": "High", - "exploitCodeMaturity": null, - "remediationLevel": null, - "reportConfidence": null, - "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" - }, - "cvssScore": 8.1, - "severity": "HIGH", - "cves": [ - "CVE-2022-23305" - ], - "unique": false - }, - { - "id": "SNYK-JAVA-LOG4J-2342646", - "title": "Deserialization of Untrusted Data", - "source": "snyk", - "cvss": { - "attackVector": "Network", - "attackComplexity": "High", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "High", - "availabilityImpact": "High", - "exploitCodeMaturity": null, - "remediationLevel": null, - "reportConfidence": null, - "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" - }, - "cvssScore": 8.1, - "severity": "HIGH", - "cves": [ - "CVE-2022-23307" - ], - "unique": false - }, - { - "id": "SNYK-JAVA-LOG4J-2342647", - "title": "Deserialization of Untrusted Data", - "source": "snyk", - "cvss": { - "attackVector": "Network", - "attackComplexity": "High", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "High", - "availabilityImpact": "High", - "exploitCodeMaturity": null, - "remediationLevel": null, - "reportConfidence": null, - "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" - }, - "cvssScore": 8.1, - "severity": "HIGH", - "cves": [ - "CVE-2022-23302" - ], - "unique": false - }, - { - "id": "SNYK-JAVA-LOG4J-2316893", - "title": "Arbitrary Code Execution", - "source": "snyk", - "cvss": { - "attackVector": "Network", - "attackComplexity": "High", - "privilegesRequired": "High", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "High", - "availabilityImpact": "High", - "exploitCodeMaturity": "Proof of concept code", - "remediationLevel": null, - "reportConfidence": null, - "cvss": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H/E:P" - }, - "cvssScore": 6.6, - "severity": "MEDIUM", - "cves": [ - "CVE-2021-4104" - ], - "unique": false - }, - { - "id": "SNYK-JAVA-LOG4J-3358774", - "title": "Denial of Service (DoS)", - "source": "snyk", - "cvss": { - "attackVector": "Network", - "attackComplexity": "High", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "None", - "integrityImpact": "None", - "availabilityImpact": "High", - "exploitCodeMaturity": null, - "remediationLevel": null, - "reportConfidence": null, - "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H" - }, - "cvssScore": 5.9, - "severity": "MEDIUM", - "cves": [ - "CVE-2023-26464" - ], - "unique": false - }, - { - "id": "SNYK-JAVA-LOG4J-1300176", - "title": "Man-in-the-Middle (MitM)", - "source": "snyk", - "cvss": { - "attackVector": "Network", - "attackComplexity": "High", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "Low", - "integrityImpact": "None", - "availabilityImpact": "None", - "exploitCodeMaturity": null, - "remediationLevel": null, - "reportConfidence": null, - "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N" - }, - "cvssScore": 3.7, - "severity": "LOW", - "cves": [ - "CVE-2020-9488" - ], - "unique": false - } - ], - "transitive": [], - "remediations": {}, - "recommendation": null - } - ] -} diff --git a/src/it/simple-modular/src/test/resources/it_poms/analysis-report.mixed b/src/it/simple-modular/src/test/resources/it_poms/analysis-report.mixed deleted file mode 100644 index b4a5641a..00000000 --- a/src/it/simple-modular/src/test/resources/it_poms/analysis-report.mixed +++ /dev/null @@ -1,604 +0,0 @@ -------=_Part_32_249990646.1688049072578 -Content-Type: application/json -Content-Transfer-Encoding: binary - -{"summary":{"dependencies":{"scanned":1,"transitive":0},"vulnerabilities":{"total":7,"direct":1,"critical":1,"high":3,"medium":2,"low":1},"providerStatuses":[{"ok":true,"provider":"snyk","status":200,"message":"OK"}]},"dependencies":[{"ref":"pkg:maven/log4j/log4j@1.2.17","highestVulnerability":{"id":"SNYK-JAVA-LOG4J-1300176","title":"Man-in-the-Middle (MitM)","source":"snyk","cvss":{"attackVector":"Network","attackComplexity":"High","privilegesRequired":"None","userInteraction":"None","scope":"Unchanged","confidentialityImpact":"Low","integrityImpact":"None","availabilityImpact":"None","exploitCodeMaturity":null,"remediationLevel":null,"reportConfidence":null,"cvss":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N"},"cvssScore":3.7,"severity":"LOW","cves":["CVE-2020-9488"],"unique":false},"issues":[{"id":"SNYK-JAVA-LOG4J-572732","title":"Deserialization of Untrusted Data","source":"snyk","cvss":{"attackVector":"Network","attackComplexity":"Low","privilegesRequired":"None","userInteraction":"None","scope":"Unchanged","confidentialityImpact":"High","integrityImpact":"High","availabilityImpact":"High","exploitCodeMaturity":"Proof of concept code","remediationLevel":null,"reportConfidence":null,"cvss":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:P"},"cvssScore":9.8,"severity":"CRITICAL","cves":["CVE-2019-17571"],"unique":false},{"id":"SNYK-JAVA-LOG4J-2342645","title":"SQL Injection","source":"snyk","cvss":{"attackVector":"Network","attackComplexity":"High","privilegesRequired":"None","userInteraction":"None","scope":"Unchanged","confidentialityImpact":"High","integrityImpact":"High","availabilityImpact":"High","exploitCodeMaturity":null,"remediationLevel":null,"reportConfidence":null,"cvss":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"cvssScore":8.1,"severity":"HIGH","cves":["CVE-2022-23305"],"unique":false},{"id":"SNYK-JAVA-LOG4J-2342646","title":"Deserialization of Untrusted Data","source":"snyk","cvss":{"attackVector":"Network","attackComplexity":"High","privilegesRequired":"None","userInteraction":"None","scope":"Unchanged","confidentialityImpact":"High","integrityImpact":"High","availabilityImpact":"High","exploitCodeMaturity":null,"remediationLevel":null,"reportConfidence":null,"cvss":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"cvssScore":8.1,"severity":"HIGH","cves":["CVE-2022-23307"],"unique":false},{"id":"SNYK-JAVA-LOG4J-2342647","title":"Deserialization of Untrusted Data","source":"snyk","cvss":{"attackVector":"Network","attackComplexity":"High","privilegesRequired":"None","userInteraction":"None","scope":"Unchanged","confidentialityImpact":"High","integrityImpact":"High","availabilityImpact":"High","exploitCodeMaturity":null,"remediationLevel":null,"reportConfidence":null,"cvss":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"cvssScore":8.1,"severity":"HIGH","cves":["CVE-2022-23302"],"unique":false},{"id":"SNYK-JAVA-LOG4J-2316893","title":"Arbitrary Code Execution","source":"snyk","cvss":{"attackVector":"Network","attackComplexity":"High","privilegesRequired":"High","userInteraction":"None","scope":"Unchanged","confidentialityImpact":"High","integrityImpact":"High","availabilityImpact":"High","exploitCodeMaturity":"Proof of concept code","remediationLevel":null,"reportConfidence":null,"cvss":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H/E:P"},"cvssScore":6.6,"severity":"MEDIUM","cves":["CVE-2021-4104"],"unique":false},{"id":"SNYK-JAVA-LOG4J-3358774","title":"Denial of Service (DoS)","source":"snyk","cvss":{"attackVector":"Network","attackComplexity":"High","privilegesRequired":"None","userInteraction":"None","scope":"Unchanged","confidentialityImpact":"None","integrityImpact":"None","availabilityImpact":"High","exploitCodeMaturity":null,"remediationLevel":null,"reportConfidence":null,"cvss":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"},"cvssScore":5.9,"severity":"MEDIUM","cves":["CVE-2023-26464"],"unique":false},{"id":"SNYK-JAVA-LOG4J-1300176","title":"Man-in-the-Middle (MitM)","source":"snyk","cvss":{"attackVector":"Network","attackComplexity":"High","privilegesRequired":"None","userInteraction":"None","scope":"Unchanged","confidentialityImpact":"Low","integrityImpact":"None","availabilityImpact":"None","exploitCodeMaturity":null,"remediationLevel":null,"reportConfidence":null,"cvss":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N"},"cvssScore":3.7,"severity":"LOW","cves":["CVE-2020-9488"],"unique":false}],"transitive":[],"remediations":{},"recommendation":null}]} -------=_Part_32_249990646.1688049072578 -Content-Type: text/html -Content-Transfer-Encoding: 8bit -Content-Disposition: attachment; filename=report.html - - - - Combined Shape - - - - - - - - - - - - - - - - - - - - - - - - - - - Dependency Analysis - - - -
-
- - - - - -   Security Issues - -
-
-
-
-

Below is a list of dependencies affected with CVE, as well as vulnerability only found using Snyk's vulnerability database.

-
-
-

Dependencies with security issues in your stack.

-

Dependencies with high common vulnerabilities and exposures (CVE) score.

-

- - - - - - Total Vulnerabilities: 7 -

-

- - - - - - Vulnerable Dependencies: 1 -

-
-
-
-
- -
-
Commonly Known Vulnerabilities
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#Dependencies# Direct# TransitiveHighest CVSSHighest SeverityRed Hat remediation available
-
-

Details of the dependency: - log4j:log4j -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SeverityExploit MaturityDescriptionCVSSCVERemediation
- - CRITICAL - - - Proof of concept codeDeserialization of Untrusted Data -
-
9.8/10
-
-
-
-
- -
- CVE-2019-17571 - - - SNYK-JAVA-LOG4J-572732 - -
- - HIGH - - - No known exploitSQL Injection -
-
8.1/10
-
-
-
-
- -
- CVE-2022-23305 - - - SNYK-JAVA-LOG4J-2342645 - -
- - HIGH - - - No known exploitDeserialization of Untrusted Data -
-
8.1/10
-
-
-
-
- -
- CVE-2022-23307 - - - SNYK-JAVA-LOG4J-2342646 - -
- - HIGH - - - No known exploitDeserialization of Untrusted Data -
-
8.1/10
-
-
-
-
- -
- CVE-2022-23302 - - - SNYK-JAVA-LOG4J-2342647 - -
- - MEDIUM - - - Proof of concept codeArbitrary Code Execution -
-
6.6/10
-
-
-
-
- -
- CVE-2021-4104 - - - SNYK-JAVA-LOG4J-2316893 - -
- - MEDIUM - - - No known exploitDenial of Service (DoS) -
-
5.9/10
-
-
-
-
- -
- CVE-2023-26464 - - - SNYK-JAVA-LOG4J-3358774 - -
- - LOW - - - No known exploitMan-in-the-Middle (MitM) -
-
3.7/10
-
-
-
-
- -
- CVE-2020-9488 - - - SNYK-JAVA-LOG4J-1300176 - -
-
- -
- - - - - - - - - - - - - - -
DependenciesSeverityExploit MaturityDescriptionCVSSCVERemediation
-
-
-
-
- - - - - - - - - - -------=_Part_32_249990646.1688049072578-- diff --git a/src/it/simple-modular/src/test/resources/it_poms/pom.xml b/src/it/simple-modular/src/test/resources/it_poms/pom.xml deleted file mode 100644 index 20f8bdfc..00000000 --- a/src/it/simple-modular/src/test/resources/it_poms/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - pom-with-deps-no-ignore - pom-with-dependency-not-ignored-for-tests - 0.0.1 - - - - log4j - log4j - 1.2.17 - - - - diff --git a/src/main/java/com/redhat/exhort/providers/JavaMavenProvider.java b/src/main/java/com/redhat/exhort/providers/JavaMavenProvider.java index 0409b492..f6a5bf44 100644 --- a/src/main/java/com/redhat/exhort/providers/JavaMavenProvider.java +++ b/src/main/java/com/redhat/exhort/providers/JavaMavenProvider.java @@ -36,6 +36,7 @@ import com.github.packageurl.PackageURL; import com.redhat.exhort.Api; import com.redhat.exhort.Provider; +import com.redhat.exhort.impl.ExhortApi; import com.redhat.exhort.logging.LoggersFactory; import com.redhat.exhort.sbom.Sbom; import com.redhat.exhort.sbom.SbomFactory; @@ -469,7 +470,7 @@ private List getDependencies(final Path manifestPath) thro } private Map getMvnExecEnvs() { - var javaHome = System.getProperty("JAVA_HOME"); + var javaHome = ExhortApi.getStringValueEnvironment("JAVA_HOME",""); if (javaHome != null && !javaHome.isBlank()) { return Collections.singletonMap("JAVA_HOME", javaHome); } diff --git a/src/main/java/com/redhat/exhort/providers/JavaMavenProvider.java.orig b/src/main/java/com/redhat/exhort/providers/JavaMavenProvider.java.orig new file mode 100644 index 00000000..dd105180 --- /dev/null +++ b/src/main/java/com/redhat/exhort/providers/JavaMavenProvider.java.orig @@ -0,0 +1,544 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort.providers; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.github.packageurl.MalformedPackageURLException; +import com.github.packageurl.PackageURL; +import com.redhat.exhort.Api; +import com.redhat.exhort.Provider; +import com.redhat.exhort.logging.LoggersFactory; +import com.redhat.exhort.sbom.Sbom; +import com.redhat.exhort.sbom.SbomFactory; +import com.redhat.exhort.tools.Ecosystem; +import com.redhat.exhort.tools.Ecosystem.Type; +import com.redhat.exhort.tools.Operations; + +import static com.redhat.exhort.impl.ExhortApi.debugLoggingIsNeeded; + +/** + * Concrete implementation of the {@link Provider} used for converting dependency trees + * for Java Maven projects (pom.xml) into a content Dot Graphs for Stack analysis or Json for + * Component analysis. + **/ +public final class JavaMavenProvider extends Provider { + + private Logger log = LoggersFactory.getLogger(this.getClass().getName()); + public static void main(String[] args) throws IOException { + JavaMavenProvider javaMavenProvider = new JavaMavenProvider(); + PackageURL packageURL = javaMavenProvider.parseDep("+- org.assertj:assertj-core:jar:3.24.2:test"); + LocalDateTime start = LocalDateTime.now(); + System.out.print(start); + Content content = javaMavenProvider.provideStack(Path.of("/tmp/devfile-sample-java-springboot-basic/pom.xml")); + +// PackageURL packageURL = javaMavenProvider.parseDep("pom-with-deps-no-ignore:pom-with-dependency-not-ignored-common-paths:jar:0.0.1"); +// String report = new String(content.buffer); + System.out.println(new String(content.buffer)); + LocalDateTime end = LocalDateTime.now(); + System.out.print(end); + System.out.print("Total time elapsed = " + start.until(end, ChronoUnit.NANOS)); + + } + public JavaMavenProvider() { + super(Type.MAVEN); + } + + + + @Override + public Content provideStack(final Path manifestPath) throws IOException { + // check for custom mvn executable + var mvn = Operations.getCustomPathOrElse("mvn"); + // clean command used to clean build target + var mvnCleanCmd = new String[]{mvn, "clean", "-f", manifestPath.toString()}; + var mvnEnvs = getMvnExecEnvs(); + // execute the clean command + Operations.runProcess(mvnCleanCmd, mvnEnvs); + // create a temp file for storing the dependency tree in + var tmpFile = Files.createTempFile("exhort_dot_graph_", null); + // the tree command will build the project and create the dependency tree in the temp file + var mvnTreeCmd = new ArrayList() {{ + add(mvn); + add("org.apache.maven.plugins:maven-dependency-plugin:3.6.0:tree"); + add("-Dverbose"); + add("-DoutputType=text"); + add(String.format("-DoutputFile=%s", tmpFile.toString())); + add("-f"); + add(manifestPath.toString()); + }}; + // if we have dependencies marked as ignored, exclude them from the tree command + var ignored = getDependencies(manifestPath).stream() + .filter(d -> d.ignored) + .map(DependencyAggregator::toPurl) + .map(PackageURL::getCoordinates) + .collect(Collectors.toList()); + // execute the tree command + Operations.runProcess(mvnTreeCmd.toArray(String[]::new), mvnEnvs); + if(debugLoggingIsNeeded()) + { + String stackAnalysisDependencyTree = Files.readString(tmpFile); + log.info(String.format("Package Manager Maven Stack Analysis Dependency Tree Output: %s %s",System.lineSeparator(),stackAnalysisDependencyTree)); + } + var sbom = buildSbomFromTextFormat(tmpFile); + // build and return content for constructing request to the backend + // build and return content for constructing request to the backend + return new Content(sbom.filterIgnoredDeps(ignored).getAsJsonString().getBytes(), Api.CYCLONEDX_MEDIA_TYPE); + } + + private Sbom buildSbomFromTextFormat(Path textFormatFile) throws IOException { + var sbom = SbomFactory.newInstance(Sbom.BelongingCondition.PURL,"sensitive"); + List lines = Files.readAllLines(textFormatFile); + var root = lines.get(0); + var rootPurl = parseDep(root); + sbom.addRoot(rootPurl); + lines.remove(0); + String[] array = new String[lines.size()]; + lines.toArray(array); +// createSbomIteratively(lines,sbom); + parseDependencyTree(root, 0 , array, sbom); + return sbom; + } + + private void parseDependencyTree(String src, int srcDepth, String [] lines, Sbom sbom) { + if(lines.length == 0) { + return; + } + if(lines.length == 1 && lines[0].trim().equals("")){ + return; + } + int index = 0; + String target = lines[index]; + int targetDepth = getDepth(target); + while(targetDepth > srcDepth && index < lines.length ) + { + if(targetDepth == srcDepth + 1) { + PackageURL from = parseDep(src); + PackageURL to = parseDep(target); + if(dependencyIsNotTestScope(from) && dependencyIsNotTestScope(to)) { + sbom.addDependency(from, to); + } + } + else { + String[] modifiedLines = Arrays.copyOfRange(lines, index, lines.length); + parseDependencyTree(lines[index-1],getDepth(lines[index-1]),modifiedLines,sbom); + } + if(index< lines.length - 1) { + target = lines[++index]; + targetDepth = getDepth(target); + } + else + { + index++; + } + } + } + + private static boolean dependencyIsNotTestScope(PackageURL artifact) { + return (Objects.nonNull(artifact.getQualifiers()) && !artifact.getQualifiers().get("scope").equals("test")) || Objects.isNull(artifact.getQualifiers()); + } +// private void createSbomIteratively(List lines,Sbom sbom) +// { +// String[] rows = new String[lines.size()]; +// lines.toArray(rows); +// for (String line : lines) { +// +// int depth = getDepth(line); +// PackageURL packageURL = parseDep(line); +// int startSearchExcluding = lines.indexOf(line); +// String[] theLines = Arrays.copyOfRange(rows, startSearchExcluding + 1, lines.size()); +// boolean notCollectedAll= true; +// for (int i = 0; i < theLines.length && notCollectedAll ; i++) { +// int targetDepth = getDepth(theLines[i]); +// PackageURL target; +// if(targetDepth == depth + 1) +// { +// target = parseDep(theLines[i]); +// sbom.addDependency(packageURL,target); +// } +// else if(targetDepth <= depth) +// { +// notCollectedAll = false; +// } +// } +// } +// } + + private int getDepth(String line) { + if(line == null || line.trim().equals("")){ + return -1; + } + + if(line.matches("^\\w.*")) + { + return 0; + } + + return ( (line.indexOf('-') -1 ) / 3) + 1; + } + + public PackageURL parseDep(String dep) { + //root package + DependencyAggregator dependencyAggregator = new DependencyAggregator(); + // in case line in dependency tree text starts with a letter ( for root artifact). + if(dep.matches("^\\w.*")) + { + dependencyAggregator = new DependencyAggregator(); + String[] parts = dep.split(":"); + dependencyAggregator.groupId = parts[0]; + dependencyAggregator.artifactId = parts[1]; + dependencyAggregator.version = parts[3]; + + return dependencyAggregator.toPurl(); + + } + int firstDash = dep.indexOf("-"); + String dependency = dep.substring(++firstDash).trim(); + if(dependency.startsWith("(")) + { + dependency = dependency.substring(1); + } + dependency = dependency.replace(":runtime", ":compile").replace(":provided", ":compile"); + int endIndex = Math.max(dependency.indexOf(":compile"),dependency.indexOf(":test")); + int scopeLength; + if(dependency.indexOf(":compile") > -1) { + scopeLength = ":compile".length(); + } + else { + scopeLength = ":test".length(); + } + dependency = dependency.substring(0,endIndex + scopeLength); + String[] parts = dependency.split(":"); + // contains only GAV + packaging + scope + if(parts.length == 5) + { + dependencyAggregator.groupId = parts[0]; + dependencyAggregator.artifactId= parts[1]; + dependencyAggregator.version = parts[3]; + + String conflictMessage = "omitted for conflict with"; + if (dep.contains(conflictMessage)) + { + dependencyAggregator.version = dep.substring(dep.indexOf(conflictMessage) + conflictMessage.length()).replace(")", "").trim(); + } + } + // In case there are 6 parts, there is also a classifier for artifact (version suffix) + // contains GAV + packaging + classifier + scope + else if(parts.length == 6) + { + dependencyAggregator.groupId = parts[0]; + dependencyAggregator.artifactId= parts[1]; + dependencyAggregator.version = String.format("%s-%s",parts[4],parts[3]); + String conflictMessage = "omitted for conflict with"; + if (dep.contains(conflictMessage)) + { + dependencyAggregator.version = dep.substring(dep.indexOf(conflictMessage) + conflictMessage.length()).replace(")", "").trim(); + } + + } + else{ + throw new RuntimeException(String.format("Cannot parse dependency into PackageUrl from line = \"%s\"",dep)); + } + if(parts[parts.length - 1].matches(".*[a-z]$")) { + dependencyAggregator.scope = parts[parts.length - 1]; + } + else { + int endOfLine = Integer.min(parts[parts.length - 1].indexOf(""), parts[parts.length - 1].indexOf("-")); + dependencyAggregator.scope = parts[parts.length - 1].substring(0, endOfLine).trim(); + } + return dependencyAggregator.toPurl(); + } + + private PackageURL txtPkgToPurl(String dotPkg) { + var parts = dotPkg. + replaceAll("\"", "") + .trim().split(":"); + if(parts.length >= 4) { + try { + return new PackageURL(Ecosystem.Type.MAVEN.getType(), parts[0], parts[1], parts[3], null, null); + } catch (MalformedPackageURLException e) { + throw new IllegalArgumentException("Unable to parse dot package: " + dotPkg, e); + } + } + throw new IllegalArgumentException("Invalid dot package format: " + dotPkg); + } + + @Override + public Content provideComponent(byte[] manifestContent) throws IOException { + // save content in temporary file + var originPom = Files.createTempFile("exhort_orig_pom_", ".xml"); + Files.write(originPom, manifestContent); + // build effective pom command + Content content = generateSbomFromEffectivePom(originPom); + Files.delete(originPom); + return content; + } + + private Content generateSbomFromEffectivePom(Path originPom) throws IOException { + // check for custom mvn executable + var mvn = Operations.getCustomPathOrElse("mvn"); + var tmpEffPom = Files.createTempFile("exhort_eff_pom_", ".xml"); + var mvnEffPomCmd = new String[]{ + mvn, + "clean", + "help:effective-pom", + String.format("-Doutput=%s", tmpEffPom.toString()), + "-f", originPom.toString() + }; + // execute the effective pom command + Operations.runProcess(mvnEffPomCmd, getMvnExecEnvs()); + if(debugLoggingIsNeeded()) + { + String CaEffectivePoM = Files.readString(tmpEffPom); + log.info(String.format("Package Manager Maven Component Analysis Effective POM Output : %s %s",System.lineSeparator(),CaEffectivePoM)); + } + // if we have dependencies marked as ignored grab ignored dependencies from the original pom + // the effective-pom goal doesn't carry comments + List dependencies = getDependencies(originPom); + var ignored = dependencies.stream().filter(d -> d.ignored).map(DependencyAggregator::toPurl).collect(Collectors.toSet()); + var testsDeps = dependencies.stream().filter(DependencyAggregator::isTestDependency).collect(Collectors.toSet()); + var deps = getDependencies(tmpEffPom); + var sbom = SbomFactory.newInstance().addRoot(getRoot(tmpEffPom)); + deps.stream() + .filter(dep -> !testsDeps.contains(dep)) + .map(DependencyAggregator::toPurl) + .filter(dep -> ignored.stream().filter(artifact -> artifact.isCoordinatesEquals(dep)).collect(Collectors.toList()).size() == 0) + .forEach(d -> sbom.addDependency(sbom.getRoot(), d)); + + // build and return content for constructing request to the backend + return new Content(sbom.getAsJsonString().getBytes(), Api.CYCLONEDX_MEDIA_TYPE); + } + + @Override + public Content provideComponent(Path manifestPath) throws IOException { + Content content = generateSbomFromEffectivePom(manifestPath); + return content; + } + + private PackageURL getRoot(final Path manifestPath) throws IOException { + XMLStreamReader reader = null; + try { + reader = XMLInputFactory.newInstance().createXMLStreamReader(Files.newInputStream(manifestPath)); + DependencyAggregator dependencyAggregator = null; + boolean isRoot = false; + while (reader.hasNext()) { + reader.next(); // get the next event + if (reader.isStartElement() && "project".equals(reader.getLocalName())) { + isRoot = true; + dependencyAggregator = new DependencyAggregator(); + continue; + } + if (!Objects.isNull(dependencyAggregator)) { + if (reader.isStartElement()) { + switch (reader.getLocalName()) { + case "groupId": // starting "groupId" tag, get next event and set to aggregator + reader.next(); + dependencyAggregator.groupId = reader.getText(); + break; + case "artifactId": // starting "artifactId" tag, get next event and set to aggregator + reader.next(); + dependencyAggregator.artifactId = reader.getText(); + break; + case "version": // starting "version" tag, get next event and set to aggregator + reader.next(); + dependencyAggregator.version = reader.getText(); + break; + } + } + if (isRoot && dependencyAggregator.isValid()) { + return dependencyAggregator.toPurl(); + } + } + } + } catch (XMLStreamException exc) { + throw new IOException(exc); + } finally { + if (!Objects.isNull(reader)) { + try { + reader.close(); // close stream if open + } catch (XMLStreamException e) { + // + } + } + } + + throw new IllegalStateException("Unable to retrieve Root dependency from effective pom"); + } + + private List getDependencies(final Path manifestPath) throws IOException { + List deps = new ArrayList<>(); + XMLStreamReader reader = null; + try { + //get a xml stream reader for the manifest file + reader = XMLInputFactory.newInstance().createXMLStreamReader(Files.newInputStream(manifestPath)); + // the following dependencyIgnore object is used to aggregate dependency data over iterations + // when a "dependency" tag starts, it will be initiated, + // when a "dependency" tag ends, it will be parsed, act upon, and reset + DependencyAggregator dependencyAggregator = null; + while (reader.hasNext()) { + reader.next(); // get the next event + if (reader.isStartElement() && "dependency".equals(reader.getLocalName())) { + // starting "dependency" tag, initiate aggregator + dependencyAggregator = new DependencyAggregator(); + continue; + } + + // if dependency aggregator haven't been initiated, + // we're currently not iterating over a "dependency" tag - no need for further parsing + if (!Objects.isNull(dependencyAggregator)) { + // if we hit an ignore comment, mark aggregator to be ignored + if (reader.getEventType() == XMLStreamConstants.COMMENT + && "exhortignore".equals(reader.getText().strip()) + ) { + dependencyAggregator.ignored = true; + continue; + } + + if (reader.isStartElement()) { + // NOTE if we want to include "scope" tags in ignore, + // add a case here and a property in DependencyIgnore + switch (reader.getLocalName()) { + case "groupId": // starting "groupId" tag, get next event and set to aggregator + reader.next(); + dependencyAggregator.groupId = reader.getText(); + break; + case "artifactId": // starting "artifactId" tag, get next event and set to aggregator + reader.next(); + dependencyAggregator.artifactId = reader.getText(); + break; + + case "scope": + reader.next(); + dependencyAggregator.scope = reader.getText() != null ? reader.getText().trim() : "*"; + break; + case "version": // starting "version" tag, get next event and set to aggregator + reader.next(); + dependencyAggregator.version = reader.getText(); + break; + } + } + + if (reader.isEndElement() && "dependency".equals(reader.getLocalName())) { + // add object to list and reset dependency aggregator + deps.add(dependencyAggregator); + dependencyAggregator = null; + } + } + } + } catch (XMLStreamException exc) { + throw new IOException(exc); + } finally { + if (!Objects.isNull(reader)) { + try { + reader.close(); // close stream if open + } catch (XMLStreamException e) { + // + } + } + } + + return deps; + } + + private Map getMvnExecEnvs() { +<<<<<<< HEAD + var javaHome = System.getProperty("JAVA_HOME"); +======= + var javaHome = ExhortApi.getStringValueEnvironment("JAVA_HOME",""); +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + if (javaHome != null && !javaHome.isBlank()) { + return Collections.singletonMap("JAVA_HOME", javaHome); + } + return null; + } + + // NOTE if we want to include "scope" tags in ignore, + // add property here and a case in the start-element-switch in the getIgnored method + /** Aggregator class for aggregating Dependency data over stream iterations, **/ + private final static class DependencyAggregator { + private String scope="*"; + private String groupId; + private String artifactId; + private String version; + boolean ignored = false; + + /** + * Get the string representation of the dependency to use as excludes + * @return an exclude string for the dependency:tree plugin, ie. group-id:artifact-id:*:version + */ + @Override + public String toString() { + // NOTE if you add scope, don't forget to replace the * with its value + return String.format("%s:%s:%s:%s", groupId, artifactId,scope, version); + } + + public boolean isValid() { + return Objects.nonNull(groupId) && Objects.nonNull(artifactId) && Objects.nonNull(version); + } + + public boolean isTestDependency() + { + return scope.trim().equals("test"); + } + + /** + * Convert the {@link DependencyAggregator} object to a {@link PackageAggregator} + * @return a new instance of {@link PackageAggregator} + * @throws MalformedPackageURLException + */ + public PackageURL toPurl() { + try { + return new PackageURL(Ecosystem.Type.MAVEN.getType(), groupId, artifactId, version, this.scope == "*" ? null :new TreeMap<>(Map.of("scope",this.scope)), null); + } catch (MalformedPackageURLException e) { + throw new IllegalArgumentException("Unable to parse PackageURL", e); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DependencyAggregator)) return false; + var that = (DependencyAggregator) o; + // NOTE we do not compare the ignored field + // This is required for comparing pom.xml with effective_pom.xml as the latter doesn't + // contain comments indicating ignore + return Objects.equals(this.groupId, that.groupId) && + Objects.equals(this.artifactId, that.artifactId) && + Objects.equals(this.version, that.version); + + } + + @Override + public int hashCode() { + return Objects.hash(groupId, artifactId, version); + } + } +} diff --git a/src/test/java/com/redhat/exhort/ExhortTest.java b/src/test/java/com/redhat/exhort/ExhortTest.java index 17cbb175..74efe977 100644 --- a/src/test/java/com/redhat/exhort/ExhortTest.java +++ b/src/test/java/com/redhat/exhort/ExhortTest.java @@ -19,13 +19,14 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Objects; public class ExhortTest { protected String getStringFromFile(String... list) { byte[] bytes = new byte[0]; try { - InputStream resourceAsStream = ExhortTest.class.getModule().getResourceAsStream(String.join("/", list)); + InputStream resourceAsStream = getResourceAsStreamDecision(this.getClass(), list); bytes = resourceAsStream.readAllBytes(); resourceAsStream.close(); } catch (IOException e) { @@ -35,13 +36,29 @@ protected String getStringFromFile(String... list) { return new String(bytes); } + public static InputStream getResourceAsStreamDecision(Class theClass, String[] list) throws IOException { + InputStream resourceAsStreamFromModule = theClass.getModule().getResourceAsStream(String.join("/", list)); + if (Objects.isNull(resourceAsStreamFromModule)) { + return theClass.getClassLoader().getResourceAsStream(String.join("/", list)); + } + return resourceAsStreamFromModule; + } + protected String getFileFromResource(String fileName, String... pathList) { Path tmpFile; try { var tmpDir = Files.createTempDirectory("exhort_test_"); tmpFile = Files.createFile(tmpDir.resolve(fileName)); - try (var is = getClass().getModule().getResourceAsStream(String.join("/", pathList))) { - Files.write(tmpFile, is.readAllBytes()); + try (var is = getResourceAsStreamDecision(this.getClass(), pathList)) { + if(Objects.nonNull(is)) { + Files.write(tmpFile, is.readAllBytes()); + } + else + { + InputStream resourceIs = getClass().getClassLoader().getResourceAsStream(String.join("/", pathList)); + Files.write(tmpFile, resourceIs.readAllBytes()); + resourceIs.close(); + } } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/test/java/com/redhat/exhort/ExhortTest.java.orig b/src/test/java/com/redhat/exhort/ExhortTest.java.orig new file mode 100644 index 00000000..5ab08ced --- /dev/null +++ b/src/test/java/com/redhat/exhort/ExhortTest.java.orig @@ -0,0 +1,87 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +public class ExhortTest { + + protected String getStringFromFile(String... list) { + byte[] bytes = new byte[0]; + try { + InputStream resourceAsStream = getResourceAsStreamDecision(this.getClass(), list); + bytes = resourceAsStream.readAllBytes(); + resourceAsStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return new String(bytes); + } + + public static InputStream getResourceAsStreamDecision(Class theClass, String[] list) throws IOException { + InputStream resourceAsStreamFromModule = theClass.getModule().getResourceAsStream(String.join("/", list)); + if (Objects.isNull(resourceAsStreamFromModule)) { + return theClass.getClassLoader().getResourceAsStream(String.join("/", list)); + } + return resourceAsStreamFromModule; + } + + protected String getFileFromResource(String fileName, String... pathList) { + Path tmpFile; + try { + var tmpDir = Files.createTempDirectory("exhort_test_"); + tmpFile = Files.createFile(tmpDir.resolve(fileName)); +<<<<<<< HEAD + try (var is = getResourceAsStreamDecision(pathList)) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), pathList)) { +>>>>>>> java-enhanced-it-working + if(Objects.nonNull(is)) { + Files.write(tmpFile, is.readAllBytes()); + } + else + { + InputStream resourceIs = getClass().getClassLoader().getResourceAsStream(String.join("/", pathList)); + Files.write(tmpFile, resourceIs.readAllBytes()); + resourceIs.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return tmpFile.toString(); + } +protected String getFileFromString(String fileName, String content) { + Path tmpFile; + try { + var tmpDir = Files.createTempDirectory("exhort_test_"); + tmpFile = Files.createFile(tmpDir.resolve(fileName)); + Files.write(tmpFile, content.getBytes()); + + } catch (IOException e) { + throw new RuntimeException(e); + } + return tmpFile.toString(); + } + +} diff --git a/src/test/java/com/redhat/exhort/impl/ExhortApiIT.java b/src/test/java/com/redhat/exhort/impl/ExhortApiIT.java new file mode 100644 index 00000000..95fab6fa --- /dev/null +++ b/src/test/java/com/redhat/exhort/impl/ExhortApiIT.java @@ -0,0 +1,219 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.redhat.exhort.Api; +import com.redhat.exhort.ExhortTest; +import com.redhat.exhort.api.AnalysisReport; +import com.redhat.exhort.api.ProviderReport; +import com.redhat.exhort.providers.HelperExtension; +import com.redhat.exhort.tools.Ecosystem; +import com.redhat.exhort.tools.Operations; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.MockedStatic; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mockStatic; + +@Tag("IntegrationTest") +@ExtendWith(HelperExtension.class) +@ExtendWith(MockitoExtension.class) +class ExhortApiIT extends ExhortTest { + + private static Api api; + private static Map ecoSystemsManifestNames; + + private MockedStatic mockedOperations; + @BeforeAll + static void beforeAll() { + api = new ExhortApi(); + System.setProperty("RHDA_SOURCE","exhort-java-api-it"); + System.setProperty("EXHORT_DEV_MODE","false"); + ecoSystemsManifestNames = Map.of("golang", "go.mod","maven","pom.xml","npm","package.json","pypi","requirements.txt"); + + } + + @Tag("IntegrationTest") + @AfterAll + static void afterAll() { + System.clearProperty("RHDA_SOURCE"); + System.clearProperty("EXHORT_DEV_MODE"); + api = null; + } + @Tag("IntegrationTest") + @ParameterizedTest + @EnumSource(value = Ecosystem.Type.class, names = { "GOLANG", "MAVEN", "NPM", "PYTHON" }) + void Integration_Test_End_To_End_Stack_Analysis(Ecosystem.Type packageManager) throws IOException, ExecutionException, InterruptedException { + String manifestFileName = ecoSystemsManifestNames.get(packageManager.getType()); + String pathToManifest = getFileFromResource(manifestFileName, "tst_manifests", "it", packageManager.getType(), manifestFileName); + preparePythonEnvironment(packageManager); + // Github action runner with all maven and java versions seems to enter infinite loop in integration tests of MAVEN when runnig dependency maven plugin to produce verbose text dependenct tree format. + // locally it's not recreated with same versions + mockMavenDependencyTree(packageManager); + AnalysisReport analysisReportResult = api.stackAnalysis(pathToManifest).get(); + handleJsonResponse(analysisReportResult,true); + releaseStaticMock(packageManager); + } + + private void releaseStaticMock(Ecosystem.Type packageManager) { + if(packageManager.equals(Ecosystem.Type.MAVEN)) { + this.mockedOperations.close(); + } + } + + + @Tag("IntegrationTest") + @ParameterizedTest + @EnumSource(value = Ecosystem.Type.class, names = { "GOLANG", "MAVEN", "NPM", "PYTHON" }) + void Integration_Test_End_To_End_Stack_Analysis_Mixed(Ecosystem.Type packageManager) throws IOException, ExecutionException, InterruptedException { + String manifestFileName = ecoSystemsManifestNames.get(packageManager.getType()); + String pathToManifest = getFileFromResource(manifestFileName, "tst_manifests", "it", packageManager.getType(), manifestFileName); + preparePythonEnvironment(packageManager); + // Github action runner with all maven and java versions seems to enter infinite loop in integration tests of MAVEN when runnig dependency maven plugin to produce verbose text dependenct tree format. + // locally it's not recreated with same versions + mockMavenDependencyTree(packageManager); + AnalysisReport analysisReportJson = api.stackAnalysisMixed(pathToManifest).get().json; + String analysisReportHtml = new String(api.stackAnalysisMixed(pathToManifest).get().html); + handleJsonResponse(analysisReportJson,true); + handleHtmlResponse(analysisReportHtml); + releaseStaticMock(packageManager); + } + + @Tag("IntegrationTest") + @ParameterizedTest + @EnumSource(value = Ecosystem.Type.class, names = { "GOLANG", "MAVEN", "NPM", "PYTHON" }) + void Integration_Test_End_To_End_Stack_Analysis_Html(Ecosystem.Type packageManager) throws IOException, ExecutionException, InterruptedException { + String manifestFileName = ecoSystemsManifestNames.get(packageManager.getType()); + String pathToManifest = getFileFromResource(manifestFileName, "tst_manifests", "it", packageManager.getType(), manifestFileName); + preparePythonEnvironment(packageManager); + // Github action runner with all maven and java versions seems to enter infinite loop in integration tests of MAVEN when runnig dependency maven plugin to produce verbose text dependenct tree format. + // locally it's not recreated with same versions + mockMavenDependencyTree(packageManager); + String analysisReportHtml = new String(api.stackAnalysisHtml(pathToManifest).get()); + releaseStaticMock(packageManager); + handleHtmlResponse(analysisReportHtml); + } + + + @Tag("IntegrationTest") + @ParameterizedTest + @EnumSource(value = Ecosystem.Type.class, names = { "GOLANG", "MAVEN", "NPM", "PYTHON" }) + void Integration_Test_End_To_End_Component_Analysis(Ecosystem.Type packageManager) throws IOException, ExecutionException, InterruptedException { + String manifestFileName = ecoSystemsManifestNames.get(packageManager.getType()); + byte[] manifestContent = getStringFromFile("tst_manifests", "it", packageManager.getType(), manifestFileName).getBytes(); + preparePythonEnvironment(packageManager); + AnalysisReport analysisReportResult = api.componentAnalysis(manifestFileName,manifestContent).get(); + handleJsonResponse(analysisReportResult,false); + } + + + private static void preparePythonEnvironment(Ecosystem.Type packageManager) { + if(packageManager.equals(Ecosystem.Type.PYTHON)) { + System.setProperty("EXHORT_PYTHON_VIRTUAL_ENV","true"); + System.setProperty("EXHORT_PYTHON_INSTALL_BEST_EFFORTS","true"); + System.setProperty("MATCH_MANIFEST_VERSIONS","false"); + } + else { + System.clearProperty("EXHORT_PYTHON_VIRTUAL_ENV"); + System.clearProperty("EXHORT_PYTHON_INSTALL_BEST_EFFORTS"); + System.clearProperty("MATCH_MANIFEST_VERSIONS"); + } + } + + private static void handleJsonResponse(AnalysisReport analysisReportResult, boolean positiveNumberOfTransitives) { + analysisReportResult.getProviders().entrySet().stream().forEach(provider -> { assertTrue(provider.getValue().getStatus().getOk()); + assertTrue(provider.getValue().getStatus().getCode() == HttpURLConnection.HTTP_OK); + }); + analysisReportResult.getProviders().entrySet().stream() + .map(Map.Entry::getValue) + .map(ProviderReport::getSources) + .map(Map::entrySet) + .flatMap(Collection::stream) + .map(Map.Entry::getValue) + .forEach( source -> assertTrue(source.getSummary().getTotal() > 0 )); + + if(positiveNumberOfTransitives) { + assertTrue(analysisReportResult.getScanned().getTransitive() > 0); + } + else { + assertEquals(0,analysisReportResult.getScanned().getTransitive()); + } + } + + private void handleHtmlResponse(String analysisReportHtml) throws JsonProcessingException { + ObjectMapper om = new ObjectMapper(); + assertTrue(analysisReportHtml.contains("svg") && analysisReportHtml.contains("html")); + int jsonStart = analysisReportHtml.indexOf("\"report\":"); + int jsonEnd = analysisReportHtml.indexOf("}}}}}"); + String embeddedJson = analysisReportHtml.substring(jsonStart + 9 ,jsonEnd + 5); + JsonNode jsonInHtml = om.readTree(embeddedJson); + JsonNode scannedNode = jsonInHtml.get("scanned"); + assertTrue(scannedNode.get("total").asInt(0) > 0); + assertTrue(scannedNode.get("transitive").asInt(0) > 0); + JsonNode status = jsonInHtml.get("providers").get("snyk").get("status"); + assertTrue(status.get("code").asInt(0) == 200); + assertTrue(status.get("ok").asBoolean(false)); + + } + private void mockMavenDependencyTree(Ecosystem.Type packageManager) throws IOException { + if(packageManager.equals(Ecosystem.Type.MAVEN)) { + mockedOperations = mockStatic(Operations.class); + String depTree; + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "it","maven", "depTree.txt"})) { + depTree = new String(is.readAllBytes()); + } + mockedOperations.when(() -> Operations.runProcess(any(),any())).thenAnswer(invocationOnMock -> { return getOutputFileAndOverwriteItWithMock(depTree, invocationOnMock, "-DoutputFile");}); + } + } + + public static String getOutputFileAndOverwriteItWithMock(String outputFileContent, InvocationOnMock invocationOnMock, String parameterPrefix) throws IOException { + String[] rawArguments = (String[]) invocationOnMock.getRawArguments()[0]; + Optional outputFileArg = Arrays.stream(rawArguments).filter(arg -> arg!= null && arg.startsWith(parameterPrefix)).findFirst(); + String outputFilePath=null; + if(outputFileArg.isPresent()) + { + String outputFile = outputFileArg.get(); + outputFilePath = outputFile.substring(outputFile.indexOf("=") + 1); + Files.writeString(Path.of(outputFilePath), outputFileContent); + } + return outputFilePath; + } + +} + + + diff --git a/src/test/java/com/redhat/exhort/impl/ExhortApiIT.java.orig b/src/test/java/com/redhat/exhort/impl/ExhortApiIT.java.orig new file mode 100644 index 00000000..97467047 --- /dev/null +++ b/src/test/java/com/redhat/exhort/impl/ExhortApiIT.java.orig @@ -0,0 +1,226 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.redhat.exhort.Api; +import com.redhat.exhort.ExhortTest; +import com.redhat.exhort.api.AnalysisReport; +import com.redhat.exhort.api.ProviderReport; +import com.redhat.exhort.providers.HelperExtension; +import com.redhat.exhort.tools.Ecosystem; +import com.redhat.exhort.tools.Operations; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.MockedStatic; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mockStatic; + +@Tag("IntegrationTest") +@ExtendWith(HelperExtension.class) +@ExtendWith(MockitoExtension.class) +class ExhortApiIT extends ExhortTest { + + private static Api api; + private static Map ecoSystemsManifestNames; + + private MockedStatic mockedOperations; + @BeforeAll + static void beforeAll() { + api = new ExhortApi(); +<<<<<<< HEAD + System.setProperty("EXHORT_DEV_MODE","true"); +======= + System.setProperty("RHDA_SOURCE","exhort-java-api-it"); + System.setProperty("EXHORT_DEV_MODE","false"); +>>>>>>> java-enhanced-it-working + ecoSystemsManifestNames = Map.of("golang", "go.mod","maven","pom.xml","npm","package.json","pypi","requirements.txt"); + + } + + @Tag("IntegrationTest") + @AfterAll + static void afterAll() { +<<<<<<< HEAD +======= + System.clearProperty("RHDA_SOURCE"); +>>>>>>> java-enhanced-it-working + System.clearProperty("EXHORT_DEV_MODE"); + api = null; + } + @Tag("IntegrationTest") + @ParameterizedTest + @EnumSource(value = Ecosystem.Type.class, names = { "GOLANG", "MAVEN", "NPM", "PYTHON" }) + void Integration_Test_End_To_End_Stack_Analysis(Ecosystem.Type packageManager) throws IOException, ExecutionException, InterruptedException { + String manifestFileName = ecoSystemsManifestNames.get(packageManager.getType()); + String pathToManifest = getFileFromResource(manifestFileName, "tst_manifests", "it", packageManager.getType(), manifestFileName); + preparePythonEnvironment(packageManager); + // Github action runner with all maven and java versions seems to enter infinite loop in integration tests of MAVEN when runnig dependency maven plugin to produce verbose text dependenct tree format. + // locally it's not recreated with same versions + mockMavenDependencyTree(packageManager); + AnalysisReport analysisReportResult = api.stackAnalysis(pathToManifest).get(); + handleJsonResponse(analysisReportResult,true); + releaseStaticMock(packageManager); + } + + private void releaseStaticMock(Ecosystem.Type packageManager) { + if(packageManager.equals(Ecosystem.Type.MAVEN)) { + this.mockedOperations.close(); + } + } + + + @Tag("IntegrationTest") + @ParameterizedTest + @EnumSource(value = Ecosystem.Type.class, names = { "GOLANG", "MAVEN", "NPM", "PYTHON" }) + void Integration_Test_End_To_End_Stack_Analysis_Mixed(Ecosystem.Type packageManager) throws IOException, ExecutionException, InterruptedException { + String manifestFileName = ecoSystemsManifestNames.get(packageManager.getType()); + String pathToManifest = getFileFromResource(manifestFileName, "tst_manifests", "it", packageManager.getType(), manifestFileName); + preparePythonEnvironment(packageManager); + // Github action runner with all maven and java versions seems to enter infinite loop in integration tests of MAVEN when runnig dependency maven plugin to produce verbose text dependenct tree format. + // locally it's not recreated with same versions + mockMavenDependencyTree(packageManager); + AnalysisReport analysisReportJson = api.stackAnalysisMixed(pathToManifest).get().json; + String analysisReportHtml = new String(api.stackAnalysisMixed(pathToManifest).get().html); + handleJsonResponse(analysisReportJson,true); + handleHtmlResponse(analysisReportHtml); + releaseStaticMock(packageManager); + } + + @Tag("IntegrationTest") + @ParameterizedTest + @EnumSource(value = Ecosystem.Type.class, names = { "GOLANG", "MAVEN", "NPM", "PYTHON" }) + void Integration_Test_End_To_End_Stack_Analysis_Html(Ecosystem.Type packageManager) throws IOException, ExecutionException, InterruptedException { + String manifestFileName = ecoSystemsManifestNames.get(packageManager.getType()); + String pathToManifest = getFileFromResource(manifestFileName, "tst_manifests", "it", packageManager.getType(), manifestFileName); + preparePythonEnvironment(packageManager); + // Github action runner with all maven and java versions seems to enter infinite loop in integration tests of MAVEN when runnig dependency maven plugin to produce verbose text dependenct tree format. + // locally it's not recreated with same versions + mockMavenDependencyTree(packageManager); + String analysisReportHtml = new String(api.stackAnalysisHtml(pathToManifest).get()); + releaseStaticMock(packageManager); + handleHtmlResponse(analysisReportHtml); + } + + + @Tag("IntegrationTest") + @ParameterizedTest + @EnumSource(value = Ecosystem.Type.class, names = { "GOLANG", "MAVEN", "NPM", "PYTHON" }) + void Integration_Test_End_To_End_Component_Analysis(Ecosystem.Type packageManager) throws IOException, ExecutionException, InterruptedException { + String manifestFileName = ecoSystemsManifestNames.get(packageManager.getType()); + byte[] manifestContent = getStringFromFile("tst_manifests", "it", packageManager.getType(), manifestFileName).getBytes(); + preparePythonEnvironment(packageManager); + AnalysisReport analysisReportResult = api.componentAnalysis(manifestFileName,manifestContent).get(); + handleJsonResponse(analysisReportResult,false); + } + + + private static void preparePythonEnvironment(Ecosystem.Type packageManager) { + if(packageManager.equals(Ecosystem.Type.PYTHON)) { + System.setProperty("EXHORT_PYTHON_VIRTUAL_ENV","true"); + System.setProperty("EXHORT_PYTHON_INSTALL_BEST_EFFORTS","true"); + System.setProperty("MATCH_MANIFEST_VERSIONS","false"); + } + else { + System.clearProperty("EXHORT_PYTHON_VIRTUAL_ENV"); + System.clearProperty("EXHORT_PYTHON_INSTALL_BEST_EFFORTS"); + System.clearProperty("MATCH_MANIFEST_VERSIONS"); + } + } + + private static void handleJsonResponse(AnalysisReport analysisReportResult, boolean positiveNumberOfTransitives) { + analysisReportResult.getProviders().entrySet().stream().forEach(provider -> { assertTrue(provider.getValue().getStatus().getOk()); + assertTrue(provider.getValue().getStatus().getCode() == HttpURLConnection.HTTP_OK); + }); + analysisReportResult.getProviders().entrySet().stream() + .map(Map.Entry::getValue) + .map(ProviderReport::getSources) + .map(Map::entrySet) + .flatMap(Collection::stream) + .map(Map.Entry::getValue) + .forEach( source -> assertTrue(source.getSummary().getTotal() > 0 )); + + if(positiveNumberOfTransitives) { + assertTrue(analysisReportResult.getScanned().getTransitive() > 0); + } + else { + assertEquals(0,analysisReportResult.getScanned().getTransitive()); + } + } + + private void handleHtmlResponse(String analysisReportHtml) throws JsonProcessingException { + ObjectMapper om = new ObjectMapper(); + assertTrue(analysisReportHtml.contains("svg") && analysisReportHtml.contains("html")); + int jsonStart = analysisReportHtml.indexOf("\"report\":"); + int jsonEnd = analysisReportHtml.indexOf("}}}}}"); + String embeddedJson = analysisReportHtml.substring(jsonStart + 9 ,jsonEnd + 5); + JsonNode jsonInHtml = om.readTree(embeddedJson); + JsonNode scannedNode = jsonInHtml.get("scanned"); + assertTrue(scannedNode.get("total").asInt(0) > 0); + assertTrue(scannedNode.get("transitive").asInt(0) > 0); + JsonNode status = jsonInHtml.get("providers").get("snyk").get("status"); + assertTrue(status.get("code").asInt(0) == 200); + assertTrue(status.get("ok").asBoolean(false)); + + } + private void mockMavenDependencyTree(Ecosystem.Type packageManager) throws IOException { + if(packageManager.equals(Ecosystem.Type.MAVEN)) { + mockedOperations = mockStatic(Operations.class); + String depTree; + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "it","maven", "depTree.txt"})) { + depTree = new String(is.readAllBytes()); + } + mockedOperations.when(() -> Operations.runProcess(any(),any())).thenAnswer(invocationOnMock -> { return getOutputFileAndOverwriteItWithMock(depTree, invocationOnMock, "-DoutputFile");}); + } + } + + public static String getOutputFileAndOverwriteItWithMock(String outputFileContent, InvocationOnMock invocationOnMock, String parameterPrefix) throws IOException { + String[] rawArguments = (String[]) invocationOnMock.getRawArguments()[0]; + Optional outputFileArg = Arrays.stream(rawArguments).filter(arg -> arg!= null && arg.startsWith(parameterPrefix)).findFirst(); + String outputFilePath=null; + if(outputFileArg.isPresent()) + { + String outputFile = outputFileArg.get(); + outputFilePath = outputFile.substring(outputFile.indexOf("=") + 1); + Files.writeString(Path.of(outputFilePath), outputFileContent); + } + return outputFilePath; + } + +} + + + diff --git a/src/test/java/com/redhat/exhort/impl/Exhort_Api_Test.java b/src/test/java/com/redhat/exhort/impl/Exhort_Api_Test.java index fb1c95d0..5ac3e8c6 100644 --- a/src/test/java/com/redhat/exhort/impl/Exhort_Api_Test.java +++ b/src/test/java/com/redhat/exhort/impl/Exhort_Api_Test.java @@ -15,6 +15,7 @@ */ package com.redhat.exhort.impl; +import static com.redhat.exhort.ExhortTest.getResourceAsStreamDecision; import static org.assertj.core.api.BDDAssertions.then; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; @@ -30,6 +31,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import com.redhat.exhort.ExhortTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -54,7 +56,7 @@ @ClearEnvironmentVariable(key="RHDA_TOKEN") @ClearEnvironmentVariable(key="RHDA_SOURCE") @SuppressWarnings("unchecked") -class Exhort_Api_Test { +class Exhort_Api_Test extends ExhortTest { @Mock Provider mockProvider; @Mock @@ -75,7 +77,7 @@ void stackAnalysisHtml_with_pom_xml_should_return_html_report_from_the_backend() throws IOException, ExecutionException, InterruptedException { // create a temporary pom.xml file var tmpFile = Files.createTempFile("exhort_test_pom_", ".xml"); - try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { Files.write(tmpFile, is.readAllBytes()); } @@ -97,7 +99,7 @@ void stackAnalysisHtml_with_pom_xml_should_return_html_report_from_the_backend() // load dummy html and set as the expected analysis byte[] expectedHtml; - try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.html")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.html"})) { expectedHtml = is.readAllBytes(); } @@ -134,7 +136,7 @@ void stackAnalysis_with_pom_xml_should_return_json_object_from_the_backend() throws IOException, ExecutionException, InterruptedException { // create a temporary pom.xml file var tmpFile = Files.createTempFile("exhort_test_pom_", ".xml"); - try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { Files.write(tmpFile, is.readAllBytes()); } @@ -159,7 +161,7 @@ void stackAnalysis_with_pom_xml_should_return_json_object_from_the_backend() // load dummy json and set as the expected analysis var mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); AnalysisReport expectedAnalysis; - try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.json")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.json"})) { expectedAnalysis = mapper.readValue(is, AnalysisReport.class); } @@ -191,7 +193,7 @@ void componentAnalysis_with_pom_xml_should_return_json_object_from_the_backend() throws IOException, ExecutionException, InterruptedException { // load pom.xml byte[] targetPom; - try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { targetPom = is.readAllBytes(); } @@ -218,7 +220,7 @@ void componentAnalysis_with_pom_xml_should_return_json_object_from_the_backend() // load dummy json and set as the expected analysis var mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); AnalysisReport expectedReport; - try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.json")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.json"})) { expectedReport = mapper.readValue(is, AnalysisReport.class); } @@ -249,19 +251,19 @@ void stackAnalysisMixed_with_pom_xml_should_return_both_html_text_and_json_objec // load dummy json and set as the expected analysis var mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); AnalysisReport expectedJson; - try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.json")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.json"})) { expectedJson = mapper.readValue(is, AnalysisReport.class); } // load dummy html and set as the expected analysis byte[] expectedHtml; - try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.html")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.html"})) { expectedHtml = is.readAllBytes(); } // create a temporary pom.xml file var tmpFile = Files.createTempFile("exhort_test_pom_", ".xml"); - try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { Files.write(tmpFile, is.readAllBytes()); } @@ -277,7 +279,7 @@ void stackAnalysisMixed_with_pom_xml_should_return_both_html_text_and_json_objec // load dummy mixed and set as the expected analysis byte[] mixedResponse; - try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.mixed")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.mixed"})) { mixedResponse = is.readAllBytes(); } @@ -310,7 +312,7 @@ void componentAnalysis_with_pom_xml_as_path_should_return_json_object_from_the_b throws IOException, ExecutionException, InterruptedException { // load pom.xml var tmpFile = Files.createTempFile("exhort_test_pom_", ".xml"); - try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { Files.write(tmpFile, is.readAllBytes()); } @@ -332,7 +334,7 @@ void componentAnalysis_with_pom_xml_as_path_should_return_json_object_from_the_b // load dummy json and set as the expected analysis var mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); AnalysisReport expectedReport; - try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.json")) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.json"})) { expectedReport = mapper.readValue(is, AnalysisReport.class); } diff --git a/src/test/java/com/redhat/exhort/impl/Exhort_Api_Test.java.orig b/src/test/java/com/redhat/exhort/impl/Exhort_Api_Test.java.orig new file mode 100644 index 00000000..8b3c1527 --- /dev/null +++ b/src/test/java/com/redhat/exhort/impl/Exhort_Api_Test.java.orig @@ -0,0 +1,517 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort.impl; + +import static com.redhat.exhort.ExhortTest.getResourceAsStreamDecision; +import static org.assertj.core.api.BDDAssertions.then; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import java.io.IOException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import com.redhat.exhort.ExhortTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.ClearEnvironmentVariable; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.redhat.exhort.Provider; +import com.redhat.exhort.api.AnalysisReport; +import com.redhat.exhort.tools.Ecosystem; + +@ExtendWith(MockitoExtension.class) +@ClearEnvironmentVariable(key="EXHORT_SNYK_TOKEN") +@ClearEnvironmentVariable(key="EXHORT_DEV_MODE") +@ClearEnvironmentVariable(key="DEV_EXHORT_BACKEND_URL") +@ClearEnvironmentVariable(key="RHDA_TOKEN") +@ClearEnvironmentVariable(key="RHDA_SOURCE") +@SuppressWarnings("unchecked") +class Exhort_Api_Test extends ExhortTest { + @Mock + Provider mockProvider; + @Mock + HttpClient mockHttpClient; + @InjectMocks + ExhortApi exhortApiSut; + + @AfterEach + void cleanup() { + System.clearProperty("EXHORT_SNYK_TOKEN"); + } + + @Test + @SetEnvironmentVariable(key="EXHORT_SNYK_TOKEN", value="snyk-token-from-env-var") + @SetEnvironmentVariable(key="RHDA_TOKEN", value="rhda-token-from-env-var") + @SetEnvironmentVariable(key="RHDA_SOURCE", value="rhda-source-from-env-var") + void stackAnalysisHtml_with_pom_xml_should_return_html_report_from_the_backend() + throws IOException, ExecutionException, InterruptedException { + // create a temporary pom.xml file + var tmpFile = Files.createTempFile("exhort_test_pom_", ".xml"); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpFile, is.readAllBytes()); + } + + // stub the mocked provider with a fake content object + given(mockProvider.provideStack(tmpFile)) + .willReturn(new Provider.Content("fake-body-content".getBytes(), "fake-content-type")); + + // create an argument matcher to make sure we mock the response to for right request + ArgumentMatcher matchesRequest = r -> + r.headers().firstValue("Content-Type").get().equals("fake-content-type") && + r.headers().firstValue("Accept").get().equals("text/html") && + // snyk token is set using the environment variable (annotation) + r.headers().firstValue("ex-snyk-token").get().equals("snyk-token-from-env-var") && + r.headers().firstValue("rhda-token").get().equals("rhda-token-from-env-var") && + r.headers().firstValue("rhda-source").get().equals("rhda-source-from-env-var") && + r.headers().firstValue("rhda-operation-type").get().equals("Stack Analysis") && + + r.method().equals("POST"); + + // load dummy html and set as the expected analysis + byte[] expectedHtml; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.html")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.html"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedHtml = is.readAllBytes(); + } + + // mock and http response object and stub it to return a fake body + var mockHttpResponse = mock(HttpResponse.class); + given(mockHttpResponse.body()).willReturn(expectedHtml); + given(mockHttpResponse.statusCode()).willReturn(200); + + // mock static getProvider utility function + try(var ecosystemTool = mockStatic(Ecosystem.class)) { + // stub static getProvider utility function to return our mock provider + ecosystemTool.when(() -> Ecosystem.getProvider(tmpFile)).thenReturn(mockProvider); + + // stub the http client to return our mocked response when request matches our arg matcher + given(mockHttpClient.sendAsync(argThat(matchesRequest), any())) + .willReturn(CompletableFuture.completedFuture(mockHttpResponse)); + + // when invoking the api for a html stack analysis report + var htmlTxt = exhortApiSut.stackAnalysisHtml(tmpFile.toString()); + // verify we got the correct html response + then(htmlTxt.get()).isEqualTo(expectedHtml); + } + // cleanup + Files.deleteIfExists(tmpFile); + } + + @Test +// System.setProperty("RHDA_TOKEN", "rhda-token-from-property"); +// System.setProperty("RHDA_SOURCE", "rhda-source-from-property"); + @SetEnvironmentVariable(key="EXHORT_SNYK_TOKEN", value="snyk-token-from-env-var") + @SetEnvironmentVariable(key="RHDA_TOKEN", value="rhda-token-from-env-var") + @SetEnvironmentVariable(key="RHDA_SOURCE", value="rhda-source-from-env-var") + void stackAnalysis_with_pom_xml_should_return_json_object_from_the_backend() + throws IOException, ExecutionException, InterruptedException { + // create a temporary pom.xml file + var tmpFile = Files.createTempFile("exhort_test_pom_", ".xml"); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpFile, is.readAllBytes()); + } + + // stub the mocked provider with a fake content object + given(mockProvider.provideStack(tmpFile)) + .willReturn(new Provider.Content("fake-body-content".getBytes(), "fake-content-type")); + + // we expect this to be ignored because tokens from env vars takes precedence + System.setProperty("EXHORT_SNYK_TOKEN", "snyk-token-from-property"); + + // create an argument matcher to make sure we mock the response for the right request + ArgumentMatcher matchesRequest = r -> + r.headers().firstValue("Content-Type").get().equals("fake-content-type") && + r.headers().firstValue("Accept").get().equals("application/json") && + // snyk token is set using the environment variable (annotation) - ignored the one set in properties + r.headers().firstValue("ex-snyk-token").get().equals("snyk-token-from-env-var") && + r.headers().firstValue("rhda-token").get().equals("rhda-token-from-env-var") && + r.headers().firstValue("rhda-source").get().equals("rhda-source-from-env-var") && + r.headers().firstValue("rhda-operation-type").get().equals("Stack Analysis") && + r.method().equals("POST"); + + // load dummy json and set as the expected analysis + var mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + AnalysisReport expectedAnalysis; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.json")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedAnalysis = mapper.readValue(is, AnalysisReport.class); + } + + // mock and http response object and stub it to return the expected analysis + var mockHttpResponse = mock(HttpResponse.class); + given(mockHttpResponse.body()).willReturn(mapper.writeValueAsString(expectedAnalysis)); + given(mockHttpResponse.statusCode()).willReturn(200); + + // mock static getProvider utility function + try(var ecosystemTool = mockStatic(Ecosystem.class)) { + // stub static getProvider utility function to return our mock provider + ecosystemTool.when(() -> Ecosystem.getProvider(tmpFile)).thenReturn(mockProvider); + + // stub the http client to return our mocked response when request matches our arg matcher + given(mockHttpClient.sendAsync(argThat(matchesRequest), any())) + .willReturn(CompletableFuture.completedFuture(mockHttpResponse)); + + // when invoking the api for a json stack analysis report + var responseAnalysis = exhortApiSut.stackAnalysis(tmpFile.toString()); + // verify we got the correct analysis report + then(responseAnalysis.get()).isEqualTo(expectedAnalysis); + } + // cleanup + Files.deleteIfExists(tmpFile); + } + + @Test + void componentAnalysis_with_pom_xml_should_return_json_object_from_the_backend() + throws IOException, ExecutionException, InterruptedException { + // load pom.xml + byte[] targetPom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + targetPom = is.readAllBytes(); + } + + // stub the mocked provider with a fake content object + given(mockProvider.provideComponent(targetPom)) + .willReturn(new Provider.Content("fake-body-content".getBytes(), "fake-content-type")); + + // we expect this to picked up because no env var to take precedence + System.setProperty("EXHORT_SNYK_TOKEN", "snyk-token-from-property"); + System.setProperty("RHDA_TOKEN", "rhda-token-from-property"); + System.setProperty("RHDA_SOURCE", "rhda-source-from-property"); + + // create an argument matcher to make sure we mock the response for the right request + ArgumentMatcher matchesRequest = r -> + r.headers().firstValue("Content-Type").get().equals("fake-content-type") && + r.headers().firstValue("Accept").get().equals("application/json") && + // snyk token is set using properties which is picked up because no env var specified + r.headers().firstValue("ex-snyk-token").get().equals("snyk-token-from-property") && + r.headers().firstValue("rhda-token").get().equals("rhda-token-from-property") && + r.headers().firstValue("rhda-source").get().equals("rhda-source-from-property") && + r.headers().firstValue("rhda-operation-type").get().equals("Component Analysis") && + r.method().equals("POST"); + + // load dummy json and set as the expected analysis + var mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + AnalysisReport expectedReport; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.json")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedReport = mapper.readValue(is, AnalysisReport.class); + } + + // mock and http response object and stub it to return the expected analysis + var mockHttpResponse = mock(HttpResponse.class); + given(mockHttpResponse.body()).willReturn(mapper.writeValueAsString(expectedReport)); + given(mockHttpResponse.statusCode()).willReturn(200); + + // mock static getProvider utility function + try (var ecosystemTool = mockStatic(Ecosystem.class)) { + // stub static getProvider utility function to return our mock provider + ecosystemTool.when(() -> Ecosystem.getProvider("pom.xml")).thenReturn(mockProvider); + + // stub the http client to return our mocked response when request matches our arg matcher + given(mockHttpClient.sendAsync(argThat(matchesRequest), any())) + .willReturn(CompletableFuture.completedFuture(mockHttpResponse)); + + // when invoking the api for a json stack analysis report + var responseAnalysis = exhortApiSut.componentAnalysis("pom.xml", targetPom); + // verify we got the correct analysis report + then(responseAnalysis.get()).isEqualTo(expectedReport); + } + } + + @Test + void stackAnalysisMixed_with_pom_xml_should_return_both_html_text_and_json_object_from_the_backend() + throws IOException, ExecutionException, InterruptedException { + // load dummy json and set as the expected analysis + var mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + AnalysisReport expectedJson; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.json")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedJson = mapper.readValue(is, AnalysisReport.class); + } + + // load dummy html and set as the expected analysis + byte[] expectedHtml; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.html")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.html"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedHtml = is.readAllBytes(); + } + + // create a temporary pom.xml file + var tmpFile = Files.createTempFile("exhort_test_pom_", ".xml"); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpFile, is.readAllBytes()); + } + + // stub the mocked provider with a fake content object + given(mockProvider.provideStack(tmpFile)) + .willReturn(new Provider.Content("fake-body-content".getBytes(), "fake-content-type")); + + // create an argument matcher to make sure we mock the response for the right request + ArgumentMatcher matchesRequest = r -> + r.headers().firstValue("Content-Type").get().equals("fake-content-type") && + r.headers().firstValue("Accept").get().equals("multipart/mixed") && + r.method().equals("POST"); + + // load dummy mixed and set as the expected analysis + byte[] mixedResponse; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.mixed")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.mixed"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + mixedResponse = is.readAllBytes(); + } + + // mock and http response object and stub it to return the expected analysis + var mockHttpResponse = mock(HttpResponse.class); + given(mockHttpResponse.body()).willReturn(mixedResponse); + given(mockHttpResponse.statusCode()).willReturn(200); + + // mock static getProvider utility function + try(var ecosystemTool = mockStatic(Ecosystem.class)) { + // stub static getProvider utility function to return our mock provider + ecosystemTool.when(() -> Ecosystem.getProvider(tmpFile)).thenReturn(mockProvider); + + // stub the http client to return our mocked response when request matches our arg matcher + given(mockHttpClient.sendAsync(argThat(matchesRequest), any())) + .willReturn(CompletableFuture.completedFuture(mockHttpResponse)); + + // when invoking the api for a json stack analysis mixed report + var responseAnalysis = exhortApiSut.stackAnalysisMixed(tmpFile.toString()).get(); + // verify we got the correct mixed report + then(new String(responseAnalysis.html).trim()).isEqualTo(new String(expectedHtml).trim()); + then(responseAnalysis.json).isEqualTo(expectedJson); + } + // cleanup + Files.deleteIfExists(tmpFile); + } + + @Test + void componentAnalysis_with_pom_xml_as_path_should_return_json_object_from_the_backend() + throws IOException, ExecutionException, InterruptedException { + // load pom.xml + var tmpFile = Files.createTempFile("exhort_test_pom_", ".xml"); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("tst_manifests/maven/empty/pom.xml")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests","maven","empty","pom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpFile, is.readAllBytes()); + } + + // stub the mocked provider with a fake content object + given(mockProvider.provideComponent(tmpFile)) + .willReturn(new Provider.Content("fake-body-content".getBytes(), "fake-content-type")); + + // we expect this to picked up because no env var to take precedence + System.setProperty("EXHORT_SNYK_TOKEN", "snyk-token-from-property"); + + // create an argument matcher to make sure we mock the response for the right request + ArgumentMatcher matchesRequest = r -> + r.headers().firstValue("Content-Type").get().equals("fake-content-type") && + r.headers().firstValue("Accept").get().equals("application/json") && + // snyk token is set using properties which is picked up because no env var specified + r.headers().firstValue("ex-snyk-token").get().equals("snyk-token-from-property") && + r.method().equals("POST"); + + // load dummy json and set as the expected analysis + var mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + AnalysisReport expectedReport; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream("dummy_responses/maven/analysis-report.json")) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"dummy_responses","maven","analysis-report.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedReport = mapper.readValue(is, AnalysisReport.class); + } + + // mock and http response object and stub it to return the expected analysis + var mockHttpResponse = mock(HttpResponse.class); + given(mockHttpResponse.body()).willReturn(mapper.writeValueAsString(expectedReport)); + given(mockHttpResponse.statusCode()).willReturn(200); + + // mock static getProvider utility function + try (var ecosystemTool = mockStatic(Ecosystem.class)) { + // stub static getProvider utility function to return our mock provider + ecosystemTool.when(() -> Ecosystem.getProvider(tmpFile)).thenReturn(mockProvider); + + // stub the http client to return our mocked response when request matches our arg matcher + given(mockHttpClient.sendAsync(argThat(matchesRequest), any())) + .willReturn(CompletableFuture.completedFuture(mockHttpResponse)); + + // when invoking the api for a json stack analysis report + var responseAnalysis = exhortApiSut.componentAnalysis(tmpFile.toString()); + // verify we got the correct analysis report + then(responseAnalysis.get()).isEqualTo(expectedReport); + //cleanup + Files.deleteIfExists(tmpFile); + } + } + + + @AfterEach + void afterEach() { + System.clearProperty("EXHORT_DEV_MODE"); + System.clearProperty("DEV_EXHORT_BACKEND_URL"); + System.clearProperty("RHDA_TOKEN"); + System.clearProperty("RHDA_SOURCE"); + + } + + @Test + @SetEnvironmentVariable(key="EXHORT_DEV_MODE", value="true") + @ClearEnvironmentVariable(key="DEV_EXHORT_BACKEND_URL") + void check_Exhort_Url_When_DEV_Mode_true_Both() { + System.setProperty("EXHORT_DEV_MODE","true"); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isEqualTo(ExhortApi.DEFAULT_ENDPOINT_DEV); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT); + } +@Test + @SetEnvironmentVariable(key="EXHORT_DEV_MODE", value="true") + @ClearEnvironmentVariable(key="DEV_EXHORT_BACKEND_URL") + void check_Exhort_Url_When_env_DEV_Mode_true_property_DEV_Mode_false() { + System.setProperty("EXHORT_DEV_MODE","false"); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isEqualTo(ExhortApi.DEFAULT_ENDPOINT_DEV); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT); + } + +@Test + @SetEnvironmentVariable(key="EXHORT_DEV_MODE", value="true") + @ClearEnvironmentVariable(key="DEV_EXHORT_BACKEND_URL") + void check_Exhort_Url_When_env_DEV_Mode_true_And_DEV_Exhort_Url_Set_Then_Default_DEV_Exhort_URL_Not_Selected() { + String dummyUrl = "http://dummy-url"; + System.setProperty("DEV_EXHORT_BACKEND_URL", dummyUrl); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isEqualTo(dummyUrl); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT_DEV); + } + +@Test + @SetEnvironmentVariable(key="EXHORT_DEV_MODE", value="false") + @ClearEnvironmentVariable(key="DEV_EXHORT_BACKEND_URL") +void check_Exhort_Url_When_env_DEV_Mode_false_And_DEV_Exhort_Url_Set_Then_Default_DEV_Exhort_URL_Not_Selected() { + System.setProperty("EXHORT_DEV_MODE", "false"); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isEqualTo(ExhortApi.DEFAULT_ENDPOINT); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT_DEV); + } + + + @Test + @SetEnvironmentVariable(key="EXHORT_DEV_MODE", value= "false") + void check_Exhort_Url_When_env_DEV_Mode_false_And_Property_Dev_Mode_true_Default_Exhort_URL_Selected() { + System.setProperty("EXHORT_DEV_MODE", "true"); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isEqualTo(ExhortApi.DEFAULT_ENDPOINT); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT_DEV); + } + + @Test + @SetEnvironmentVariable(key="EXHORT_DEV_MODE", value="false") + @SetEnvironmentVariable(key="DEV_EXHORT_BACKEND_URL", value="http://dummy-route") + void check_Exhort_Url_When_env_DEV_Mode_false_And_DEV_Exhort_Url_Set_Then_Default_Exhort_URL_Selected_Anyway() { + System.setProperty("EXHORT_DEV_MODE", "true"); + System.setProperty("DEV_EXHORT_BACKEND_URL","http://dummy-route2"); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isEqualTo(ExhortApi.DEFAULT_ENDPOINT); + then(exhortApi.getEndpoint()).isNotEqualTo(System.getenv("DEV_EXHORT_BACKEND_URL")); + then(exhortApi.getEndpoint()).isNotEqualTo(System.getProperty("DEV_EXHORT_BACKEND_URL")); + + } + @Test + void check_Exhort_Url_When_env_DEV_Mode_not_set_And_Property_Exhort_Dev_Mode_false_Then_Default_Exhort_URL_Selected() { + System.setProperty("EXHORT_DEV_MODE", "false"); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isEqualTo(ExhortApi.DEFAULT_ENDPOINT); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT_DEV); + } + @Test + void check_Exhort_Url_When_env_DEV_Mode_not_set_And_Property_Exhort_Dev_Mode_true_Then_Default_DEV_Exhort_URL_Selected() { + System.setProperty("EXHORT_DEV_MODE", "true"); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT); + then(exhortApi.getEndpoint()).isEqualTo(ExhortApi.DEFAULT_ENDPOINT_DEV); + } + @Test + @SetEnvironmentVariable(key="DEV_EXHORT_BACKEND_URL", value="http://dummy-route") + void check_Exhort_Url_When_env_DEV_Mode_not_set_And_Property_Exhort_Dev_Mode_true_and_Env_DEV_Exhort_Backend_Url_Set_Then_DEV_ENV_Exhort_URL_Selected() { + System.setProperty("EXHORT_DEV_MODE", "true"); + System.setProperty("DEV_EXHORT_BACKEND_URL", "http://dummy-route2"); + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT); + then(exhortApi.getEndpoint()).isNotEqualTo(ExhortApi.DEFAULT_ENDPOINT_DEV); + then(exhortApi.getEndpoint()).isNotEqualTo("http://dummy-route2"); + then(exhortApi.getEndpoint()).isEqualTo("http://dummy-route"); + } + + @Test + void check_Exhort_Url_When_Nothing_Set_Then_Default_Exhort_URL_Selected() { + ExhortApi exhortApi = new ExhortApi(); + then(exhortApi.getEndpoint()).isEqualTo(ExhortApi.DEFAULT_ENDPOINT); + + } + +} diff --git a/src/test/java/com/redhat/exhort/providers/Golang_Modules_Provider_Test.java b/src/test/java/com/redhat/exhort/providers/Golang_Modules_Provider_Test.java index 2225a8f9..542fc01e 100644 --- a/src/test/java/com/redhat/exhort/providers/Golang_Modules_Provider_Test.java +++ b/src/test/java/com/redhat/exhort/providers/Golang_Modules_Provider_Test.java @@ -18,8 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.redhat.exhort.Api; import com.redhat.exhort.ExhortTest; -import com.redhat.exhort.Provider; -import com.redhat.exhort.sbom.Sbom; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; @@ -58,12 +56,12 @@ void test_the_provideStack(String testFolder) throws IOException, InterruptedExc // create temp file hosting our sut package.json var tmpGoModulesDir = Files.createTempDirectory("exhort_test_"); var tmpGolangFile = Files.createFile(tmpGoModulesDir.resolve("go.mod")); - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "golang", testFolder, "go.mod"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "golang", testFolder, "go.mod"})) { Files.write(tmpGolangFile, is.readAllBytes()); } // load expected SBOM String expectedSbom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "golang", testFolder, "expected_sbom_stack_analysis.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "golang", testFolder, "expected_sbom_stack_analysis.json"})) { expectedSbom = new String(is.readAllBytes()); } // when providing stack content for our pom @@ -81,12 +79,12 @@ void test_the_provideStack(String testFolder) throws IOException, InterruptedExc void test_the_provideComponent(String testFolder) throws IOException, InterruptedException { // load the pom target pom file byte[] targetPom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "golang", testFolder, "go.mod"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "golang", testFolder, "go.mod"})) { targetPom = is.readAllBytes(); } // load expected SBOM String expectedSbom = ""; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "golang", testFolder, "expected_sbom_component_analysis.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "golang", testFolder, "expected_sbom_component_analysis.json"})) { expectedSbom = new String(is.readAllBytes()); } // when providing component content for our pom diff --git a/src/test/java/com/redhat/exhort/providers/Golang_Modules_Provider_Test.java.orig b/src/test/java/com/redhat/exhort/providers/Golang_Modules_Provider_Test.java.orig new file mode 100644 index 00000000..acdc63e1 --- /dev/null +++ b/src/test/java/com/redhat/exhort/providers/Golang_Modules_Provider_Test.java.orig @@ -0,0 +1,181 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort.providers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.redhat.exhort.Api; +import com.redhat.exhort.ExhortTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(HelperExtension.class) +class Golang_Modules_Provider_Test extends ExhortTest { + // test folder are located at src/test/resources/tst_manifests/npm + // each folder should contain: + // - package.json: the target manifest for testing + // - expected_sbom.json: the SBOM expected to be provided + static Stream testFolders() { + return Stream.of( + "go_mod_light_no_ignore", + "go_mod_no_ignore", + "go_mod_with_ignore", + "go_mod_with_all_ignore", + "go_mod_with_one_ignored_prefix_go", + "go_mod_no_path" + ); + } + + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideStack(String testFolder) throws IOException, InterruptedException { + // create temp file hosting our sut package.json + var tmpGoModulesDir = Files.createTempDirectory("exhort_test_"); + var tmpGolangFile = Files.createFile(tmpGoModulesDir.resolve("go.mod")); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "golang", testFolder, "go.mod"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "golang", testFolder, "go.mod"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpGolangFile, is.readAllBytes()); + } + // load expected SBOM + String expectedSbom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "golang", testFolder, "expected_sbom_stack_analysis.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "golang", testFolder, "expected_sbom_stack_analysis.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedSbom = new String(is.readAllBytes()); + } + // when providing stack content for our pom + var content = new GoModulesProvider().provideStack(tmpGolangFile); + // cleanup + Files.deleteIfExists(tmpGolangFile); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + } + + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideComponent(String testFolder) throws IOException, InterruptedException { + // load the pom target pom file + byte[] targetPom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "golang", testFolder, "go.mod"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "golang", testFolder, "go.mod"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + targetPom = is.readAllBytes(); + } + // load expected SBOM + String expectedSbom = ""; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "golang", testFolder, "expected_sbom_component_analysis.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "golang", testFolder, "expected_sbom_component_analysis.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedSbom = new String(is.readAllBytes()); + } + // when providing component content for our pom + var content = new GoModulesProvider().provideComponent(targetPom); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + + + } + + + @Test + void Test_The_ProvideComponent_Path_Should_Throw_Exception() { + + GoModulesProvider goModulesProvider = new GoModulesProvider(); + assertThatIllegalArgumentException().isThrownBy(() -> { + goModulesProvider.provideComponent(Path.of(".")); + }).withMessage("provideComponent with file system path for GoModules package manager not implemented yet"); + + + } + + @ParameterizedTest + @ValueSource(booleans = { true,false }) + void Test_Golang_Modules_with_Match_Manifest_Version(boolean MatchManifestVersionsEnabled) { + String goModPath = getFileFromResource("go.mod", "msc", "golang", "go.mod"); + GoModulesProvider goModulesProvider = new GoModulesProvider(); + + if(MatchManifestVersionsEnabled) + { + System.setProperty("MATCH_MANIFEST_VERSIONS", "true"); + RuntimeException runtimeException = assertThrows(RuntimeException.class, () -> goModulesProvider.getDependenciesSbom(Path.of(goModPath), true), "Expected getDependenciesSbom/2 to throw RuntimeException, due to version mismatch, but it didn't."); + assertTrue(runtimeException.getMessage().contains("Can't continue with analysis - versions mismatch for dependency name=github.com/google/uuid, manifest version=v1.1.0, installed Version=v1.1.1")); + System.clearProperty("MATCH_MANIFEST_VERSIONS"); + } + else + { + String sbomString = assertDoesNotThrow(() -> goModulesProvider.getDependenciesSbom(Path.of(goModPath), false).getAsJsonString()); + String actualSbomWithTSStripped = dropIgnoredKeepFormat(sbomString); + assertEquals(getStringFromFile("msc","golang","expected_sbom_ca.json").trim(), actualSbomWithTSStripped); + + System.out.println(sbomString); + } + } + + @Test + void Test_Golang_MvS_Logic_Enabled() throws IOException { + ObjectMapper om = new ObjectMapper(); + System.setProperty("EXHORT_GO_MVS_LOGIC_ENABLED", "true"); + String goModPath = getFileFromResource("go.mod", "msc", "golang","mvs_logic", "go.mod"); + GoModulesProvider goModulesProvider = new GoModulesProvider(); + String resultSbom = dropIgnoredKeepFormat(goModulesProvider.getDependenciesSbom(Path.of(goModPath),true).getAsJsonString()); + String expectedSbom = getStringFromFile("msc", "golang", "mvs_logic", "expected_sbom_stack_analysis.json").trim(); + + assertEquals(expectedSbom,resultSbom); + + // check that only one version of package golang/go.opencensus.io is in sbom for EXHORT_GO_MVS_LOGIC_ENABLED=true + assertTrue(Arrays.stream(resultSbom.split(System.lineSeparator())).filter(str -> str.contains("\"ref\" : \"pkg:golang/go.opencensus.io@")).count() == 1); + + System.clearProperty("EXHORT_GO_MVS_LOGIC_ENABLED"); + + resultSbom = dropIgnoredKeepFormat(goModulesProvider.getDependenciesSbom(Path.of(goModPath),true).getAsJsonString()); + // check that there is more than one version of package golang/go.opencensus.io in sbom for EXHORT_GO_MVS_LOGIC_ENABLED=false + assertTrue(Arrays.stream(resultSbom.split(System.lineSeparator())).filter(str -> str.contains("\"ref\" : \"pkg:golang/go.opencensus.io@")).count() > 1); + + } + + + private String dropIgnored(String s) { + return s.replaceAll("\\s+","").replaceAll("\"timestamp\":\"[a-zA-Z0-9\\-\\:]+\",", ""); + } + private String dropIgnoredKeepFormat(String s) { + return s.replaceAll("\"timestamp\" : \"[a-zA-Z0-9\\-\\:]+\",\n ", ""); + } + +} diff --git a/src/test/java/com/redhat/exhort/providers/HelperExtension.java b/src/test/java/com/redhat/exhort/providers/HelperExtension.java index a8850098..a33de94d 100644 --- a/src/test/java/com/redhat/exhort/providers/HelperExtension.java +++ b/src/test/java/com/redhat/exhort/providers/HelperExtension.java @@ -36,24 +36,24 @@ public class HelperExtension implements BeforeAllCallback, AfterAllCallback, Bef private List requirementsFiles; @Override public void afterAll(ExtensionContext extensionContext) throws Exception { - log.log(System.Logger.Level.DEBUG,"Finished all tests!!"); + log.log(System.Logger.Level.INFO,"Finished all tests!!"); } @Override public void afterEach(ExtensionContext extensionContext) throws Exception { - log.log(System.Logger.Level.DEBUG,String.format("Finished Test Method: %s", extensionContext.getRequiredTestMethod())); + log.log(System.Logger.Level.INFO,String.format("Finished Test Method: %s_%s", extensionContext.getRequiredTestMethod().getName(), extensionContext.getDisplayName())); } @Override public void beforeAll(ExtensionContext extensionContext) throws Exception { - log.log(System.Logger.Level.DEBUG,"Before all tests"); + log.log(System.Logger.Level.INFO,"Before all tests"); } @Override public void beforeEach(ExtensionContext extensionContext) throws Exception { - log.log(System.Logger.Level.DEBUG,String.format("Before Invoking Test Method: %s", extensionContext.getRequiredTestMethod())); + log.log(System.Logger.Level.INFO,String.format("Started Test Method: %s_%s", extensionContext.getRequiredTestMethod().getName(), extensionContext.getDisplayName())); } diff --git a/src/test/java/com/redhat/exhort/providers/Java_Maven_Provider_Test.java b/src/test/java/com/redhat/exhort/providers/Java_Maven_Provider_Test.java index 4db56e40..2fbde9b0 100644 --- a/src/test/java/com/redhat/exhort/providers/Java_Maven_Provider_Test.java +++ b/src/test/java/com/redhat/exhort/providers/Java_Maven_Provider_Test.java @@ -41,7 +41,9 @@ @ExtendWith(HelperExtension.class) @ExtendWith(MockitoExtension.class) -class Java_Maven_Provider_Test extends ExhortTest { +public class Java_Maven_Provider_Test extends ExhortTest { + + // private static System.Logger log = System.getLogger("Java_Maven_Provider_Test"); // test folder are located at src/test/resources/tst_manifests // each folder should contain: @@ -69,16 +71,16 @@ void test_the_provideStack(String testFolder) throws IOException, InterruptedExc // create temp file hosting our sut pom.xml var tmpPomFile = Files.createTempFile("exhort_test_", ".xml"); // log.log(System.Logger.Level.INFO,"the test folder is : " + testFolder); - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "pom.xml"))) { + try (var is = getResourceAsStreamDecision(getClass(), new String []{ "tst_manifests", "maven", testFolder, "pom.xml"})) { Files.write(tmpPomFile, is.readAllBytes()); } // load expected SBOM String expectedSbom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "expected_stack_sbom.json"))) { + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "expected_stack_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } String depTree; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "depTree.txt"))) { + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "depTree.txt"})) { depTree = new String(is.readAllBytes()); } @@ -100,7 +102,7 @@ void test_the_provideStack(String testFolder) throws IOException, InterruptedExc } - private static String getOutputFileAndOverwriteItWithMock(String outputFileContent, InvocationOnMock invocationOnMock,String parameterPrefix) throws IOException { + public static String getOutputFileAndOverwriteItWithMock(String outputFileContent, InvocationOnMock invocationOnMock,String parameterPrefix) throws IOException { String[] rawArguments = (String[]) invocationOnMock.getRawArguments()[0]; Optional outputFileArg = Arrays.stream(rawArguments).filter(arg -> arg!= null && arg.startsWith(parameterPrefix)).findFirst(); String outputFilePath=null; @@ -118,17 +120,17 @@ private static String getOutputFileAndOverwriteItWithMock(String outputFileConte void test_the_provideComponent(String testFolder) throws IOException, InterruptedException { // load the pom target pom file byte[] targetPom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "pom.xml"))) { + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "pom.xml"})) { targetPom = is.readAllBytes(); } // load expected SBOM String expectedSbom = ""; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "expected_component_sbom.json"))) { + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "expected_component_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } String effectivePom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "effectivePom.xml"))) { + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "effectivePom.xml"})) { effectivePom = new String(is.readAllBytes()); } @@ -152,17 +154,17 @@ void test_the_provideComponent_With_Path(String testFolder) throws IOException, // load the pom target pom file // create temp file hosting our sut pom.xml var tmpPomFile = Files.createTempFile("exhort_test_", ".xml"); - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "pom.xml"))) { + try (var is = getResourceAsStreamDecision(getClass(),new String [] { "tst_manifests", "maven", testFolder, "pom.xml"})) { Files.write(tmpPomFile, is.readAllBytes()); } // load expected SBOM String expectedSbom = ""; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "expected_component_sbom.json"))) { + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "expected_component_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } String effectivePom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "effectivePom.xml"))) { + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "effectivePom.xml"})) { effectivePom = new String(is.readAllBytes()); } diff --git a/src/test/java/com/redhat/exhort/providers/Java_Maven_Provider_Test.java.orig b/src/test/java/com/redhat/exhort/providers/Java_Maven_Provider_Test.java.orig new file mode 100644 index 00000000..6789f74d --- /dev/null +++ b/src/test/java/com/redhat/exhort/providers/Java_Maven_Provider_Test.java.orig @@ -0,0 +1,223 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort.providers; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Stream; + +import com.redhat.exhort.ExhortTest; +import com.redhat.exhort.tools.Operations; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mockStatic; + + +import com.redhat.exhort.Api; +import org.mockito.MockedStatic; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(HelperExtension.class) +@ExtendWith(MockitoExtension.class) +class Java_Maven_Provider_Test extends ExhortTest { +// private static System.Logger log = System.getLogger("Java_Maven_Provider_Test"); + // test folder are located at src/test/resources/tst_manifests + // each folder should contain: + // - pom.xml: the target manifest for testing + // - expected_sbom.json: the SBOM expected to be provided + static Stream testFolders() { + return Stream.of( + "pom_deps_with_no_ignore_provided_scope", + "deps_no_trivial_with_ignore", + "deps_with_ignore_on_artifact", + "deps_with_ignore_on_dependency", + "deps_with_ignore_on_group", + "deps_with_ignore_on_version", + "deps_with_ignore_on_wrong", + "deps_with_no_ignore", + "pom_deps_with_no_ignore_common_paths" + + + ); + } + + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideStack(String testFolder) throws IOException, InterruptedException { + // create temp file hosting our sut pom.xml + var tmpPomFile = Files.createTempFile("exhort_test_", ".xml"); +// log.log(System.Logger.Level.INFO,"the test folder is : " + testFolder); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "pom.xml"))) { +======= + try (var is = getResourceAsStreamDecision(getClass(), new String []{ "tst_manifests", "maven", testFolder, "pom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpPomFile, is.readAllBytes()); + } + // load expected SBOM + String expectedSbom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "expected_stack_sbom.json"))) { + expectedSbom = new String(is.readAllBytes()); + } + String depTree; + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "depTree.txt"))) { +======= + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "expected_stack_sbom.json"})) { + expectedSbom = new String(is.readAllBytes()); + } + String depTree; + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "depTree.txt"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + depTree = new String(is.readAllBytes()); + } + + MockedStatic mockedOperations = mockStatic(Operations.class); + mockedOperations.when(() -> Operations.runProcess(any(),any())).thenAnswer(invocationOnMock -> { + return getOutputFileAndOverwriteItWithMock(depTree, invocationOnMock,"-DoutputFile"); + }); + + + // when providing stack content for our pom + var content = new JavaMavenProvider().provideStack(tmpPomFile); + // cleanup + Files.deleteIfExists(tmpPomFile); + // verify expected SBOM is returned + mockedOperations.close(); + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + + } + + private static String getOutputFileAndOverwriteItWithMock(String outputFileContent, InvocationOnMock invocationOnMock,String parameterPrefix) throws IOException { + String[] rawArguments = (String[]) invocationOnMock.getRawArguments()[0]; + Optional outputFileArg = Arrays.stream(rawArguments).filter(arg -> arg!= null && arg.startsWith(parameterPrefix)).findFirst(); + String outputFilePath=null; + if(outputFileArg.isPresent()) + { + String outputFile = outputFileArg.get(); + outputFilePath = outputFile.substring(outputFile.indexOf("=") + 1); + Files.writeString(Path.of(outputFilePath), outputFileContent); + } + return outputFilePath; + } + + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideComponent(String testFolder) throws IOException, InterruptedException { + // load the pom target pom file + byte[] targetPom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "pom.xml"))) { +======= + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "pom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + targetPom = is.readAllBytes(); + } + // load expected SBOM + String expectedSbom = ""; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "expected_component_sbom.json"))) { +======= + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "expected_component_sbom.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedSbom = new String(is.readAllBytes()); + } + + String effectivePom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "effectivePom.xml"))) { +======= + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "effectivePom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + effectivePom = new String(is.readAllBytes()); + } + + MockedStatic mockedOperations = mockStatic(Operations.class); + mockedOperations.when(() -> Operations.runProcess(any(),any())).thenAnswer(invocationOnMock -> { + return getOutputFileAndOverwriteItWithMock(effectivePom, invocationOnMock,"-Doutput"); + }); + + // when providing component content for our pom + var content = new JavaMavenProvider().provideComponent(targetPom); + mockedOperations.close(); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + + } + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideComponent_With_Path(String testFolder) throws IOException, InterruptedException { + // load the pom target pom file + // create temp file hosting our sut pom.xml + var tmpPomFile = Files.createTempFile("exhort_test_", ".xml"); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "pom.xml"))) { +======= + try (var is = getResourceAsStreamDecision(getClass(),new String [] { "tst_manifests", "maven", testFolder, "pom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpPomFile, is.readAllBytes()); + } + // load expected SBOM + String expectedSbom = ""; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "expected_component_sbom.json"))) { +======= + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "expected_component_sbom.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedSbom = new String(is.readAllBytes()); + } + + String effectivePom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "maven", testFolder, "effectivePom.xml"))) { +======= + try (var is = getResourceAsStreamDecision(getClass(), new String [] { "tst_manifests", "maven", testFolder, "effectivePom.xml"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + effectivePom = new String(is.readAllBytes()); + } + + MockedStatic mockedOperations = mockStatic(Operations.class); + mockedOperations.when(() -> Operations.runProcess(any(),any())).thenAnswer(invocationOnMock -> { + return getOutputFileAndOverwriteItWithMock(effectivePom, invocationOnMock,"-Doutput"); + }); + + // when providing component content for our pom + var content = new JavaMavenProvider().provideComponent(tmpPomFile); + // verify expected SBOM is returned + mockedOperations.close(); + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + + } + + private String dropIgnored(String s) { + return s.replaceAll("\\s+","").replaceAll("\"timestamp\":\"[a-zA-Z0-9\\-\\:]+\",", ""); + } +} diff --git a/src/test/java/com/redhat/exhort/providers/Javascript_Npm_Provider_Test.java b/src/test/java/com/redhat/exhort/providers/Javascript_Npm_Provider_Test.java index b4e5379b..f573aa2f 100644 --- a/src/test/java/com/redhat/exhort/providers/Javascript_Npm_Provider_Test.java +++ b/src/test/java/com/redhat/exhort/providers/Javascript_Npm_Provider_Test.java @@ -56,20 +56,20 @@ void test_the_provideStack(String testFolder) throws IOException, InterruptedExc var tmpNpmFolder = Files.createTempDirectory("exhort_test_"); var tmpNpmFile = Files.createFile(tmpNpmFolder.resolve("package.json")); var tmpLockFile = Files.createFile(tmpNpmFolder.resolve("package-lock.json")); - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "package.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "package.json"})) { Files.write(tmpNpmFile, is.readAllBytes()); } - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "package-lock.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "package-lock.json"})) { Files.write(tmpLockFile, is.readAllBytes()); } // load expected SBOM String expectedSbom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "expected_stack_sbom.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "expected_stack_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } String npmListingStack; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "npm-ls-stack.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "npm-ls-stack.json"})) { npmListingStack = new String(is.readAllBytes()); } MockedStatic mockedOperations = mockStatic(Operations.class); @@ -94,16 +94,16 @@ void test_the_provideStack(String testFolder) throws IOException, InterruptedExc void test_the_provideComponent(String testFolder) throws IOException, InterruptedException { // load the pom target pom file byte[] targetPom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "package.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "package.json"})) { targetPom = is.readAllBytes(); } // load expected SBOM String expectedSbom = ""; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "expected_component_sbom.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "expected_component_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } String npmListingComponent; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "npm-ls-component.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "npm-ls-component.json"})) { npmListingComponent = new String(is.readAllBytes()); } @@ -138,15 +138,15 @@ void test_the_provideComponent_with_Path(String testFolder) throws Exception { // create temp file hosting our sut package.json var tmpNpmFolder = Files.createTempDirectory("exhort_test_"); var tmpNpmFile = Files.createFile(tmpNpmFolder.resolve("package.json")); - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "package.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "package.json"})) { Files.write(tmpNpmFile, is.readAllBytes()); } String expectedSbom = ""; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "expected_component_sbom.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "expected_component_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } String npmListingComponent; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "npm-ls-component.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "npm-ls-component.json"})) { npmListingComponent = new String(is.readAllBytes()); } ArgumentMatcher matchPath = path -> path == null; diff --git a/src/test/java/com/redhat/exhort/providers/Javascript_Npm_Provider_Test.java.orig b/src/test/java/com/redhat/exhort/providers/Javascript_Npm_Provider_Test.java.orig new file mode 100644 index 00000000..15d6e6c3 --- /dev/null +++ b/src/test/java/com/redhat/exhort/providers/Javascript_Npm_Provider_Test.java.orig @@ -0,0 +1,214 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort.providers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import com.redhat.exhort.ExhortTest; +import com.redhat.exhort.tools.Operations; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import com.redhat.exhort.Api; +import org.mockito.*; + +@ExtendWith(HelperExtension.class) +class Javascript_Npm_Provider_Test extends ExhortTest { + // test folder are located at src/test/resources/tst_manifests/npm + // each folder should contain: + // - package.json: the target manifest for testing + // - expected_sbom.json: the SBOM expected to be provided + static Stream testFolders() { + return Stream.of( + "deps_with_ignore", + "deps_with_no_ignore" + ); + } + + + + + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideStack(String testFolder) throws IOException, InterruptedException { + // create temp file hosting our sut package.json + var tmpNpmFolder = Files.createTempDirectory("exhort_test_"); + var tmpNpmFile = Files.createFile(tmpNpmFolder.resolve("package.json")); + var tmpLockFile = Files.createFile(tmpNpmFolder.resolve("package-lock.json")); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "package.json"))) { + Files.write(tmpNpmFile, is.readAllBytes()); + } + + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "package-lock.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "package.json"})) { + Files.write(tmpNpmFile, is.readAllBytes()); + } + + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "package-lock.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpLockFile, is.readAllBytes()); + } + // load expected SBOM + String expectedSbom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "expected_stack_sbom.json"))) { + expectedSbom = new String(is.readAllBytes()); + } + String npmListingStack; + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "npm-ls-stack.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "expected_stack_sbom.json"})) { + expectedSbom = new String(is.readAllBytes()); + } + String npmListingStack; + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "npm-ls-stack.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + npmListingStack = new String(is.readAllBytes()); + } + MockedStatic mockedOperations = mockStatic(Operations.class); + //Operations.runProcess(contains("npm i"),any()) + ArgumentMatcher matchPath = path -> path == null; + mockedOperations.when(() -> Operations.runProcessGetOutput(argThat(matchPath),any(String[].class))).thenReturn(npmListingStack); + // when providing stack content for our pom + var content = new JavaScriptNpmProvider().provideStack(tmpNpmFile); + // cleanup + Files.deleteIfExists(tmpNpmFile); + Files.deleteIfExists(tmpLockFile); + Files.deleteIfExists(tmpNpmFolder); + mockedOperations.close(); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + } + + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideComponent(String testFolder) throws IOException, InterruptedException { + // load the pom target pom file + byte[] targetPom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "package.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "package.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + targetPom = is.readAllBytes(); + } + // load expected SBOM + String expectedSbom = ""; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "expected_component_sbom.json"))) { + expectedSbom = new String(is.readAllBytes()); + } + String npmListingComponent; + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "npm-ls-component.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "expected_component_sbom.json"})) { + expectedSbom = new String(is.readAllBytes()); + } + String npmListingComponent; + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "npm-ls-component.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + npmListingComponent = new String(is.readAllBytes()); + } + +// MockedStatic javaFiles = mockStatic(Files.class); + //Operations.runProcess(contains("npm i"),any()) +// mockedOperations.when(() -> Operations.runProcessGetOutput(eq(null),any())).thenReturn(npmListingComponent); + MockedStatic mockedOperations = mockStatic(Operations.class); + mockedOperations.when(() -> Operations.runProcess(any(),any())).thenAnswer((invocationOnMock) -> { + String[] commandParts = (String [])invocationOnMock.getRawArguments()[0]; + int lastElementIsDir = commandParts.length - 1; + String packageLockJson = commandParts[lastElementIsDir] + "/package-lock.json"; + Files.createFile(Path.of(packageLockJson)); + return packageLockJson ; + }); + ArgumentMatcher matchPath = path -> path == null; + + mockedOperations.when(() -> Operations.runProcessGetOutput(argThat(matchPath),any(String[].class))).thenReturn(npmListingComponent); + // when providing component content for our pom + var content = new JavaScriptNpmProvider().provideComponent(targetPom); + mockedOperations.close(); +// javaFiles.close(); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + } +@ParameterizedTest + @MethodSource("testFolders") + void test_the_provideComponent_with_Path(String testFolder) throws Exception { + // load the pom target pom file + + // create temp file hosting our sut package.json + var tmpNpmFolder = Files.createTempDirectory("exhort_test_"); + var tmpNpmFile = Files.createFile(tmpNpmFolder.resolve("package.json")); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "package.json"))) { + Files.write(tmpNpmFile, is.readAllBytes()); + } + String expectedSbom = ""; + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "expected_component_sbom.json"))) { + expectedSbom = new String(is.readAllBytes()); + } + String npmListingComponent; + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "npm", testFolder, "npm-ls-component.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "package.json"})) { + Files.write(tmpNpmFile, is.readAllBytes()); + } + String expectedSbom = ""; + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "expected_component_sbom.json"})) { + expectedSbom = new String(is.readAllBytes()); + } + String npmListingComponent; + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "npm", testFolder, "npm-ls-component.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + npmListingComponent = new String(is.readAllBytes()); + } + ArgumentMatcher matchPath = path -> path == null; + MockedStatic mockedOperations = mockStatic(Operations.class); + mockedOperations.when(() -> Operations.runProcess(any(),any())).thenAnswer((invocationOnMock) -> { + String[] commandParts = (String [])invocationOnMock.getRawArguments()[0]; + int lastElementIsDir = commandParts.length - 1; + String packageLockJson = commandParts[lastElementIsDir] + "/package-lock.json"; + Files.createFile(Path.of(packageLockJson)); + return packageLockJson ; + }); + mockedOperations.when(() -> Operations.runProcessGetOutput(argThat(matchPath),any(String[].class))).thenReturn(npmListingComponent); + // when providing component content for our pom + var content = new JavaScriptNpmProvider().provideComponent(tmpNpmFile); + mockedOperations.close(); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + } + + private String dropIgnored(String s) { + return s.replaceAll("\\s+","").replaceAll("\"timestamp\":\"[a-zA-Z0-9\\-\\:]+\"", ""); + } +} diff --git a/src/test/java/com/redhat/exhort/providers/PythonEnvironmentExtension.java b/src/test/java/com/redhat/exhort/providers/PythonEnvironmentExtension.java index e5b7dffd..34a7fe35 100644 --- a/src/test/java/com/redhat/exhort/providers/PythonEnvironmentExtension.java +++ b/src/test/java/com/redhat/exhort/providers/PythonEnvironmentExtension.java @@ -58,7 +58,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception { // var tmpPythonModuleDir = Files.createTempDirectory("exhort_test_"); // var tmpPythonFile = Files.createFile(tmpPythonModuleDir.resolve("requirements.txt")); // Python_Provider_Test.testFolders().forEach( test -> { -// try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", test, "requirements.txt"))) { +// try (var is = getClass().getClassLoader().getResourceAsStream(String.join("/","tst_manifests", "pip", test, "requirements.txt"))) { // Files.write(tmpPythonFile, is.readAllBytes()); // pythonController.installPackage(tmpPythonFile.toAbsolutePath().toString()); // diff --git a/src/test/java/com/redhat/exhort/providers/Python_Provider_Test.java b/src/test/java/com/redhat/exhort/providers/Python_Provider_Test.java index cdec8445..1e3070aa 100644 --- a/src/test/java/com/redhat/exhort/providers/Python_Provider_Test.java +++ b/src/test/java/com/redhat/exhort/providers/Python_Provider_Test.java @@ -19,10 +19,8 @@ import com.redhat.exhort.ExhortTest; import com.redhat.exhort.utils.PythonControllerBase; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -64,12 +62,12 @@ void test_the_provideStack(String testFolder) throws IOException, InterruptedExc // create temp file hosting our sut package.json var tmpPythonModuleDir = Files.createTempDirectory("exhort_test_"); var tmpPythonFile = Files.createFile(tmpPythonModuleDir.resolve("requirements.txt")); - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "requirements.txt"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "requirements.txt"})) { Files.write(tmpPythonFile, is.readAllBytes()); } // load expected SBOM String expectedSbom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "expected_stack_sbom.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "expected_stack_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } // when providing stack content for our pom @@ -89,12 +87,12 @@ void test_the_provideStack(String testFolder) throws IOException, InterruptedExc void test_the_provideComponent(String testFolder) throws IOException, InterruptedException { // load the pom target pom file byte[] targetRequirementsTxt; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "requirements.txt"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "requirements.txt"})) { targetRequirementsTxt = is.readAllBytes(); } // load expected SBOM String expectedSbom = ""; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "expected_component_sbom.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "expected_component_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } // when providing component content for our pom @@ -114,12 +112,12 @@ void test_the_provideStack_with_properties(String testFolder) throws IOException // create temp file hosting our sut package.json var tmpPythonModuleDir = Files.createTempDirectory("exhort_test_"); var tmpPythonFile = Files.createFile(tmpPythonModuleDir.resolve("requirements.txt")); - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "requirements.txt"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "requirements.txt"})) { Files.write(tmpPythonFile, is.readAllBytes()); } // load expected SBOM String expectedSbom; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "expected_stack_sbom.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "expected_stack_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } // when providing stack content for our pom @@ -146,12 +144,12 @@ void test_the_provideStack_with_properties(String testFolder) throws IOException void test_the_provideComponent_with_properties(String testFolder) throws IOException, InterruptedException { // load the pom target pom file byte[] targetRequirementsTxt; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "requirements.txt"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "requirements.txt"})) { targetRequirementsTxt = is.readAllBytes(); } // load expected SBOM String expectedSbom = ""; - try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "expected_component_sbom.json"))) { + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests", "pip", testFolder, "expected_component_sbom.json"})) { expectedSbom = new String(is.readAllBytes()); } String pipShowContent = this.getStringFromFile("tst_manifests", "pip", "pip-show.txt"); diff --git a/src/test/java/com/redhat/exhort/providers/Python_Provider_Test.java.orig b/src/test/java/com/redhat/exhort/providers/Python_Provider_Test.java.orig new file mode 100644 index 00000000..8a8a08cf --- /dev/null +++ b/src/test/java/com/redhat/exhort/providers/Python_Provider_Test.java.orig @@ -0,0 +1,217 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * Licensed 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.redhat.exhort.providers; + +import com.redhat.exhort.Api; +import com.redhat.exhort.ExhortTest; +import com.redhat.exhort.utils.PythonControllerBase; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +@ExtendWith(PythonEnvironmentExtension.class) +class Python_Provider_Test extends ExhortTest { + + static Stream testFolders() { + return Stream.of( +"pip_requirements_txt_no_ignore", + "pip_requirements_txt_ignore" + + ); + } + +// @RegisterExtension +// private PythonEnvironmentExtension pythonEnvironmentExtension = new PythonEnvironmentExtension(); + + public Python_Provider_Test(PythonControllerBase pythonController) { + this.pythonController = pythonController; + this.pythonPipProvider = new PythonPipProvider(); + this.pythonPipProvider.setPythonController(pythonController); + } + + private PythonControllerBase pythonController; + private PythonPipProvider pythonPipProvider; + @EnabledIfEnvironmentVariable(named = "RUN_PYTHON_BIN",matches = "true") + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideStack(String testFolder) throws IOException, InterruptedException { + // create temp file hosting our sut package.json + var tmpPythonModuleDir = Files.createTempDirectory("exhort_test_"); + var tmpPythonFile = Files.createFile(tmpPythonModuleDir.resolve("requirements.txt")); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "requirements.txt"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "requirements.txt"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpPythonFile, is.readAllBytes()); + } + // load expected SBOM + String expectedSbom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "expected_stack_sbom.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "expected_stack_sbom.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedSbom = new String(is.readAllBytes()); + } + // when providing stack content for our pom + var content = this.pythonPipProvider.provideStack(tmpPythonFile); + // cleanup + Files.deleteIfExists(tmpPythonFile); + Files.deleteIfExists(tmpPythonModuleDir); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + } + + @EnabledIfEnvironmentVariable(named = "RUN_PYTHON_BIN",matches = "true") + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideComponent(String testFolder) throws IOException, InterruptedException { + // load the pom target pom file + byte[] targetRequirementsTxt; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "requirements.txt"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "requirements.txt"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + targetRequirementsTxt = is.readAllBytes(); + } + // load expected SBOM + String expectedSbom = ""; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "expected_component_sbom.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "expected_component_sbom.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedSbom = new String(is.readAllBytes()); + } + // when providing component content for our pom + var content = this.pythonPipProvider.provideComponent(targetRequirementsTxt); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + + + } + + + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideStack_with_properties(String testFolder) throws IOException, InterruptedException { + // create temp file hosting our sut package.json + var tmpPythonModuleDir = Files.createTempDirectory("exhort_test_"); + var tmpPythonFile = Files.createFile(tmpPythonModuleDir.resolve("requirements.txt")); +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "requirements.txt"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "requirements.txt"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + Files.write(tmpPythonFile, is.readAllBytes()); + } + // load expected SBOM + String expectedSbom; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "expected_stack_sbom.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "expected_stack_sbom.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedSbom = new String(is.readAllBytes()); + } + // when providing stack content for our pom + var content = this.pythonPipProvider.provideStack(tmpPythonFile); + String pipShowContent = this.getStringFromFile("tst_manifests", "pip", "pip-show.txt"); + String pipFreezeContent = this.getStringFromFile("tst_manifests", "pip", "pip-freeze-all.txt"); + String base64PipShow = new String(Base64.getEncoder().encode(pipShowContent.getBytes())); + String base64PipFreeze = new String(Base64.getEncoder().encode(pipFreezeContent.getBytes())); + System.setProperty("EXHORT_PIP_SHOW",base64PipShow); + System.setProperty("EXHORT_PIP_FREEZE",base64PipFreeze); + // cleanup + Files.deleteIfExists(tmpPythonFile); + Files.deleteIfExists(tmpPythonModuleDir); + System.clearProperty("EXHORT_PIP_SHOW"); + System.clearProperty("EXHORT_PIP_FREEZE"); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + } + + @ParameterizedTest + @MethodSource("testFolders") + void test_the_provideComponent_with_properties(String testFolder) throws IOException, InterruptedException { + // load the pom target pom file + byte[] targetRequirementsTxt; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "requirements.txt"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] { "tst_manifests", "pip", testFolder, "requirements.txt"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + targetRequirementsTxt = is.readAllBytes(); + } + // load expected SBOM + String expectedSbom = ""; +<<<<<<< HEAD + try (var is = getClass().getModule().getResourceAsStream(String.join("/","tst_manifests", "pip", testFolder, "expected_component_sbom.json"))) { +======= + try (var is = getResourceAsStreamDecision(this.getClass(), new String [] {"tst_manifests", "pip", testFolder, "expected_component_sbom.json"})) { +>>>>>>> 73f7443 (test: fix and tailor tests also for new java versions) + expectedSbom = new String(is.readAllBytes()); + } + String pipShowContent = this.getStringFromFile("tst_manifests", "pip", "pip-show.txt"); + String pipFreezeContent = this.getStringFromFile("tst_manifests", "pip", "pip-freeze-all.txt"); + String base64PipShow = new String(Base64.getEncoder().encode(pipShowContent.getBytes())); + String base64PipFreeze = new String(Base64.getEncoder().encode(pipFreezeContent.getBytes())); + System.setProperty("EXHORT_PIP_SHOW",base64PipShow); + System.setProperty("EXHORT_PIP_FREEZE",base64PipFreeze); + // when providing component content for our pom + var content = this.pythonPipProvider.provideComponent(targetRequirementsTxt); + // verify expected SBOM is returned + assertThat(content.type).isEqualTo(Api.CYCLONEDX_MEDIA_TYPE); + assertThat(dropIgnored(new String(content.buffer))) + .isEqualTo(dropIgnored(expectedSbom)); + System.clearProperty("EXHORT_PIP_SHOW"); + System.clearProperty("EXHORT_PIP_FREEZE"); + + } + + + @Test + void Test_The_ProvideComponent_Path_Should_Throw_Exception() { + assertThatIllegalArgumentException().isThrownBy(() -> { + this.pythonPipProvider.provideComponent(Path.of(".")); + }).withMessage("provideComponent with file system path for Python pip package manager is not supported"); + + + } + + private String dropIgnored(String s) { + return s.replaceAll("\\s+","").replaceAll("\"timestamp\":\"[a-zA-Z0-9\\-\\:]+\"", ""); + } +} diff --git a/src/test/resources/tst_manifests/it/golang/go.mod b/src/test/resources/tst_manifests/it/golang/go.mod new file mode 100644 index 00000000..c6911e8a --- /dev/null +++ b/src/test/resources/tst_manifests/it/golang/go.mod @@ -0,0 +1,10 @@ +module github.com/test-golang-namespace/test-golang-app + +go 1.19 + +require( + + github.com/gin-gonic/gin v1.6.0 + github.com/ipld/go-car v0.3.0 + go.elastic.co/apm v1.11.0 +) diff --git a/src/test/resources/tst_manifests/it/maven/depTree.txt b/src/test/resources/tst_manifests/it/maven/depTree.txt new file mode 100644 index 00000000..42751313 --- /dev/null +++ b/src/test/resources/tst_manifests/it/maven/depTree.txt @@ -0,0 +1,6 @@ +pom-with-deps-no-ignore:pom-with-dependency-not-ignored-for-tests:jar:0.0.1 ++- log4j:log4j:jar:1.2.17:compile ++- org.projectlombok:lombok:jar:1.16.6:compile +\- com.fasterxml.jackson.core:jackson-databind:jar:2.14.0:compile + +- com.fasterxml.jackson.core:jackson-annotations:jar:2.14.0:compile + \- com.fasterxml.jackson.core:jackson-core:jar:2.14.0:compile diff --git a/src/test/resources/tst_manifests/it/maven/pom.xml b/src/test/resources/tst_manifests/it/maven/pom.xml new file mode 100644 index 00000000..423c98bc --- /dev/null +++ b/src/test/resources/tst_manifests/it/maven/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + pom-with-deps-no-ignore + pom-with-dependency-not-ignored-for-tests + 0.0.1 + + + + log4j + log4j + 1.2.17 + + + + org.projectlombok + lombok + 1.16.6 + + + + com.fasterxml.jackson.core + jackson-databind + 2.14.0 + + + + diff --git a/src/test/resources/tst_manifests/it/npm/package-lock.json b/src/test/resources/tst_manifests/it/npm/package-lock.json new file mode 100644 index 00000000..cbb3d399 --- /dev/null +++ b/src/test/resources/tst_manifests/it/npm/package-lock.json @@ -0,0 +1,96 @@ +{ + "name": "test-app", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "test-app", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@hapi/joi": "^17.1.1", + "axios": "^0.19.0" + } + }, + "node_modules/@hapi/address": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", + "integrity": "sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==", + "deprecated": "Moved to 'npm install @sideway/address'", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@hapi/formula": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==", + "deprecated": "Moved to 'npm install @sideway/formula'" + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/joi": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", + "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", + "deprecated": "Switch to 'npm install joi'", + "dependencies": { + "@hapi/address": "^4.0.1", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" + } + }, + "node_modules/@hapi/pinpoint": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", + "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "dependencies": { + "follow-redirects": "1.5.10" + } + }, + "node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "dependencies": { + "debug": "=3.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } +} diff --git a/src/test/resources/tst_manifests/it/npm/package.json b/src/test/resources/tst_manifests/it/npm/package.json new file mode 100644 index 00000000..7e26367d --- /dev/null +++ b/src/test/resources/tst_manifests/it/npm/package.json @@ -0,0 +1,16 @@ +{ + "name": "test-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "node app.js", + "server": "nodemon server.js" + }, + "keywords": [], + "license": "ISC", + "dependencies": { + "@hapi/joi": "^17.1.1", + "axios": "^0.19.0" + } +} diff --git a/src/test/resources/tst_manifests/it/pypi/requirements.txt b/src/test/resources/tst_manifests/it/pypi/requirements.txt new file mode 100644 index 00000000..e55220b7 --- /dev/null +++ b/src/test/resources/tst_manifests/it/pypi/requirements.txt @@ -0,0 +1,3 @@ +anyio==3.6.2 +click==8.0.4 +Flask==2.0.3