diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index a6fd34568..8982a2149 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -8,33 +8,22 @@ inputs: maven-version: description: The Maven version the build will run with. required: true - mutation-testing: - description: Whether to run mutation testing or not. - default: 'true' - required: false runs: using: composite steps: - name: Set up Java ${{ inputs.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: ${{ inputs.java-version }} distribution: sapmachine cache: maven - name: Set up Maven ${{ inputs.maven-version }} - uses: stCarolas/setup-maven@v5 + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: maven-version: ${{ inputs.maven-version }} - - name: Piper Maven build - uses: SAP/project-piper-action@main - with: - step-name: mavenBuild - docker-image: '' - - - name: Mutation Testing - if: ${{ inputs.mutation-testing == 'true' }} - run: mvn org.pitest:pitest-maven:mutationCoverage -f cds-feature-attachments/pom.xml -ntp -B + - name: Maven Build + run: mvn clean install -DskipTests -B -ntp shell: bash diff --git a/.github/actions/cf-bind/action.yml b/.github/actions/cf-bind/action.yml new file mode 100644 index 000000000..7baa0410e --- /dev/null +++ b/.github/actions/cf-bind/action.yml @@ -0,0 +1,91 @@ +name: Bind Cloud Foundry Services +description: Login to CF and bind services for hybrid testing via cds bind + +inputs: + cf-api: + description: Cloud Foundry API endpoint + required: true + cf-username: + description: Cloud Foundry username + required: true + cf-password: + description: Cloud Foundry password + required: true + cf-org: + description: Cloud Foundry organization + required: true + cf-space: + description: Cloud Foundry space + required: true + auth-method: + description: 'Malware scanner authentication method: basic or mtls' + required: true + +runs: + using: composite + steps: + - name: Install CF CLI + shell: bash + run: | + wget -q "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=8.9.0&source=github-rel" -O cf-cli.tar.gz + tar -xzf cf-cli.tar.gz + sudo mv cf8 /usr/local/bin/cf + cf --version + + - name: CF Login + shell: bash + env: + CF_USERNAME: ${{ inputs.cf-username }} + CF_PASSWORD: ${{ inputs.cf-password }} + CF_API: ${{ inputs.cf-api }} + CF_ORG: ${{ inputs.cf-org }} + CF_SPACE: ${{ inputs.cf-space }} + run: | + for i in {1..5}; do + cf api "$CF_API" && \ + cf auth && \ + cf target -o "$CF_ORG" -s "$CF_SPACE" && break + if [ "$i" -eq 5 ]; then + echo "cf login failed after 5 attempts." + exit 1 + fi + echo "cf login failed, retrying ($i/5)..." + sleep 10 + done + + - name: Install @sap/cds-dk + shell: bash + run: | + npm i -g @sap/cds-dk + echo "$(npm config get prefix)/bin" >> "${GITHUB_PATH}" + + - name: Install CDS dependencies + shell: bash + run: npm i + working-directory: integration-tests/mtx-local + + - name: Bind objectstore + shell: bash + working-directory: integration-tests/mtx-local + run: | + for i in {1..5}; do + cds bind os -2 os:pipeline && break + echo "cds bind objectstore failed, retrying ($i/5)..." + sleep 30 + if [ "$i" -eq 5 ]; then + echo "cds bind objectstore failed after 5 attempts." + exit 1 + fi + done + + - name: Bind malware-scanner (basic) + if: inputs.auth-method == 'basic' + shell: bash + working-directory: integration-tests/mtx-local + run: cds bind malware -2 malware:malware-key-basic + + - name: Bind malware-scanner (mtls) + if: inputs.auth-method == 'mtls' + shell: bash + working-directory: integration-tests/mtx-local + run: cds bind malware -2 malware:malware-key-mtls diff --git a/.github/actions/deploy-release/action.yml b/.github/actions/deploy-release/action.yml index 23377a50c..7b900da65 100644 --- a/.github/actions/deploy-release/action.yml +++ b/.github/actions/deploy-release/action.yml @@ -27,14 +27,8 @@ inputs: runs: using: composite steps: - - name: Echo Inputs - run: | - echo "user: ${{ inputs.user }}" - echo "revision: ${{ inputs.revision }}" - shell: bash - - name: Set up Java - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: sapmachine java-version: '17' @@ -44,7 +38,7 @@ runs: server-password: MAVEN_CENTRAL_PASSWORD - name: Set up Maven ${{ inputs.maven-version }} - uses: stCarolas/setup-maven@v5 + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: maven-version: ${{ inputs.maven-version }} diff --git a/.github/actions/integration-tests/action.yml b/.github/actions/integration-tests/action.yml index ca4e3cfc1..2e311d8ad 100644 --- a/.github/actions/integration-tests/action.yml +++ b/.github/actions/integration-tests/action.yml @@ -1,5 +1,5 @@ -name: Integration Tests with current version of CAP Java -description: Run integration tests with the current version of CAP Java using Maven. +name: Integration Tests +description: Run integration tests using Maven with cds bind for service bindings. inputs: java-version: @@ -8,40 +8,36 @@ inputs: maven-version: description: The Maven version the build shall run with. required: true - test-type: - description: 'Which integration test to run: build-version, latest-version, or oss' - required: true runs: using: composite steps: - name: Set up Java ${{ inputs.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: ${{ inputs.java-version }} distribution: sapmachine cache: maven - name: Setup Maven ${{ inputs.maven-version }} - uses: stCarolas/setup-maven@v5 + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: maven-version: ${{ inputs.maven-version }} - name: Build dependencies for integration tests - run: mvn clean install -ntp -B -pl cds-feature-attachments,storage-targets/cds-feature-attachments-fs,storage-targets/cds-feature-attachments-oss -am + run: mvn clean install -ntp -B -pl cds-feature-attachments,storage-targets/cds-feature-attachments-fs,storage-targets/cds-feature-attachments-oss -am -Dcds.install-node.skip shell: bash - - name: Integration Tests with version of CAP Java used for build - if: inputs.test-type == 'build-version' - run: mvn clean verify -ntp -B -f ./integration-tests/pom.xml + - name: Generic Integration Tests + run: mvn clean verify -ntp -B -f integration-tests/pom.xml -pl :cds-feature-attachments-integration-tests-parent,:cds-feature-attachments-integration-tests-db,:cds-feature-attachments-integration-tests-generic -Dcds.install-node.skip shell: bash - - name: Integration Tests with latest version of CAP Java - if: inputs.test-type == 'latest-version' - run: mvn clean verify -ntp -B -f ./integration-tests/pom.xml -P latest-test-version + - name: MTX-Local Integration Tests + run: cds bind --exec -- mvn clean verify -ntp -B -f ../../integration-tests/mtx-local/srv/pom.xml -Dcds.install-node.skip + working-directory: integration-tests/mtx-local shell: bash - - name: Integration Tests for the object store service - if: inputs.test-type == 'oss' - run: mvn clean verify -ntp -B -Pintegration-tests-oss + - name: Client Integration Tests (OSS + Malware) + run: cds bind --exec -- mvn verify -ntp -B -f ../../pom.xml -pl cds-feature-attachments,storage-targets/cds-feature-attachments-oss -P integration-tests,integration-tests-oss -Dcds.install-node.skip + working-directory: integration-tests/mtx-local shell: bash diff --git a/.github/actions/newrelease/action.yml b/.github/actions/newrelease/action.yml index a6fe732c4..e9a08a852 100644 --- a/.github/actions/newrelease/action.yml +++ b/.github/actions/newrelease/action.yml @@ -13,14 +13,14 @@ runs: using: composite steps: - name: Set up Java ${{ inputs.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: ${{ inputs.java-version }} distribution: sapmachine cache: maven - name: Set up Maven ${{ inputs.maven-version }} - uses: stCarolas/setup-maven@v5 + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: maven-version: ${{ inputs.maven-version }} diff --git a/.github/actions/scan-with-blackduck/action.yml b/.github/actions/scan-with-blackduck/action.yml index 2c847e382..b9d1e21d5 100644 --- a/.github/actions/scan-with-blackduck/action.yml +++ b/.github/actions/scan-with-blackduck/action.yml @@ -24,35 +24,37 @@ runs: using: composite steps: - name: Set up Java ${{ inputs.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: ${{ inputs.java-version }} distribution: sapmachine cache: maven - name: Set up Maven ${{ inputs.maven-version }} - uses: stCarolas/setup-maven@v5 + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: maven-version: ${{ inputs.maven-version }} - - name: Get Major Version - id: get-major-version + - name: Get Revision + id: get-revision run: | echo "REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout)" >> $GITHUB_OUTPUT shell: bash - - name: Print Version Number - run: echo "${{ steps.get-major-version.outputs.REVISION }}" - shell: bash - - - name: BlackDuck Scan - uses: SAP/project-piper-action@main + - name: BlackDuck Security Scan + uses: blackduck-inc/black-duck-security-scan@v2 with: - step-name: detectExecuteScan - flags: \ - --githubToken=$GITHUB_token \ - --version=${{ steps.get-major-version.outputs.REVISION }} - env: - PIPER_token: ${{ inputs.blackduck_token }} - GITHUB_token: ${{ inputs.github_token }} - SCAN_MODE: ${{ inputs.scan_mode }} + blackducksca_url: https://sap.blackducksoftware.com/ + blackducksca_token: ${{ inputs.blackduck_token }} + blackducksca_scan_full: ${{ inputs.scan_mode == 'FULL' }} + github_token: ${{ inputs.github_token }} + detect_args: > + --detect.project.name=com.sap.cds.feature.attachments + --detect.project.version.name=${{ steps.get-revision.outputs.REVISION }} + --detect.included.detector.types=MAVEN + --detect.excluded.directories=**/node_modules,**/*test*,**/localrepo,**/target/site,**/*-site.jar,**/samples/** + --detect.maven.excluded.modules=integration-tests,integration-tests/db,integration-tests/generic,integration-tests/mtx-local/srv + --detect.maven.build.command=-pl com.sap.cds:cds-feature-attachments + --detect.tools=DETECTOR,BINARY_SCAN + --detect.risk.report.pdf=false + --logging.level.detect=INFO diff --git a/.github/actions/scan-with-codeql/action.yml b/.github/actions/scan-with-codeql/action.yml new file mode 100644 index 000000000..6ccd08f7c --- /dev/null +++ b/.github/actions/scan-with-codeql/action.yml @@ -0,0 +1,44 @@ +name: CodeQL Analysis +description: Runs CodeQL security analysis on the project. + +inputs: + java-version: + description: The Java version to use for the build. + required: true + maven-version: + description: The Maven version to use for the build. + required: true + +runs: + using: composite + steps: + - name: Set up Java ${{ inputs.java-version }} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: ${{ inputs.java-version }} + distribution: sapmachine + cache: maven + + - name: Set up Maven ${{ inputs.maven-version }} + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: ${{ inputs.maven-version }} + + - name: Initialize CodeQL + uses: github/codeql-action/init@ed410739ba306e4ebe5e123421a6bd694e494a2b # v4 + with: + languages: java-kotlin + build-mode: manual + + - name: Install @sap/cds-dk + run: npm i -g @sap/cds-dk + shell: bash + + - name: Build Java code + run: mvn clean compile -B -ntp -Dcds.install-node.skip + shell: bash + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@ed410739ba306e4ebe5e123421a6bd694e494a2b # v4 + with: + category: "/language:java-kotlin" diff --git a/.github/actions/scan-with-sonar/action.yml b/.github/actions/scan-with-sonar/action.yml index 3884b4052..abf626eef 100644 --- a/.github/actions/scan-with-sonar/action.yml +++ b/.github/actions/scan-with-sonar/action.yml @@ -20,14 +20,14 @@ runs: steps: - name: Set up Java ${{inputs.java-version}} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: ${{inputs.java-version}} distribution: sapmachine cache: maven - name: Set up Maven ${{inputs.maven-version}} - uses: stCarolas/setup-maven@v5 + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: maven-version: ${{inputs.maven-version}} @@ -37,13 +37,9 @@ runs: echo "REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout)" >> $GITHUB_OUTPUT shell: bash - - name: Print Revision - run: echo "${{steps.get-revision.outputs.REVISION}}" - shell: bash - - name: Build project for SonarQube scan run: | - mvn clean verify -ntp -B + mvn clean verify -ntp -B -Dcds.install-node.skip shell: bash - name: Verify JaCoCo reports exist @@ -66,12 +62,19 @@ runs: shell: bash - name: SonarQube Scan - uses: SAP/project-piper-action@main - with: - step-name: sonarExecuteScan - flags: > - --token=${{ inputs.sonarq-token }} - --githubToken=${{ inputs.github-token }} - --version=${{ steps.get-revision.outputs.REVISION }} - --inferJavaBinaries=true - --options=-Dsonar.exclusions=**/samples/**,-Dsonar.coverage.jacoco.xmlReportPaths=coverage-report/target/site/jacoco-aggregate/jacoco.xml + run: > + mvn org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + -Dsonar.host.url=https://sonar.tools.sap + -Dsonar.token="${SONAR_TOKEN}" + -Dsonar.projectKey=cds-feature-attachments + -Dsonar.projectVersion=${{ steps.get-revision.outputs.REVISION }} + -Dsonar.qualitygate.wait=true + -Dsonar.java.source=17 + -Dsonar.exclusions=**/samples/** + -Dsonar.coverage.jacoco.xmlReportPaths=${{ github.workspace }}/coverage-report/target/site/jacoco-aggregate/jacoco.xml + -Dsonar.coverage.exclusions=cds-feature-attachments/src/test/**,cds-feature-attachments/src/gen/**,storage-targets/cds-feature-attachments-fs/src/test/**,storage-targets/cds-feature-attachments-oss/src/test/** + -B -ntp + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + SONAR_TOKEN: ${{ inputs.sonarq-token }} diff --git a/.github/actions/test-sample/action.yml b/.github/actions/test-sample/action.yml index 403d49584..c0c8954f2 100644 --- a/.github/actions/test-sample/action.yml +++ b/.github/actions/test-sample/action.yml @@ -1,18 +1,26 @@ name: 'Test Sample' description: 'Compile sample and run tests' +inputs: + java-version: + description: The Java version the build shall run with. + required: true + maven-version: + description: The Maven version the build shall run with. + required: true + runs: using: 'composite' steps: - name: Set up Java ${{ inputs.java-version }} - uses: actions/setup-java@v4 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: ${{ inputs.java-version }} distribution: sapmachine cache: maven - name: Set up Maven ${{ inputs.maven-version }} - uses: stCarolas/setup-maven@v5 + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: maven-version: ${{ inputs.maven-version }} @@ -29,4 +37,4 @@ runs: - name: Run tests shell: bash working-directory: samples/bookshop - run: mvn test \ No newline at end of file + run: mvn test diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f5cd6b22c..8be0b3dbf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,10 +5,8 @@ updates: - "/" schedule: interval: weekly - ignore: - - dependency-name: "com.sap.cds:*" - versions: - - ">=4" + cooldown: + default-days: 7 groups: minor-patch: patterns: @@ -21,6 +19,8 @@ updates: directory: "/" schedule: interval: weekly + cooldown: + default-days: 7 groups: minor-patch: patterns: diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml index ef40728ee..f0ce52ea9 100644 --- a/.github/workflows/issue.yml +++ b/.github/workflows/issue.yml @@ -1,7 +1,6 @@ name: Label issues -permissions: - issues: write +permissions: {} on: issues: @@ -11,6 +10,8 @@ on: jobs: label_issues: runs-on: ubuntu-latest + permissions: + issues: write steps: - run: gh issue edit "$NUMBER" --add-label "$LABELS" env: @@ -19,7 +20,7 @@ jobs: NUMBER: ${{ github.event.issue.number }} LABELS: New - - uses: actions/github-script@v9 + - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 with: script: | github.rest.issues.createComment({ @@ -27,4 +28,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: `👋 Hello @${context.payload.issue.user.login}, thank you for submitting this issue. Our team is reviewing your report and will follow up with you as soon as possible.` - }) \ No newline at end of file + }) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e077dd447..cb0494971 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,11 @@ name: CI - MAIN +permissions: + actions: read + contents: read + packages: read + security-events: write + env: MAVEN_VERSION: '3.9.12' @@ -15,10 +21,10 @@ jobs: timeout-minutes: 30 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Scan With Black Duck - uses: ./.github/actions/scan-with-blackduck + uses: cap-java/cds-feature-attachments/.github/actions/scan-with-blackduck@main with: blackduck_token: ${{ secrets.BLACK_DUCK_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -26,7 +32,5 @@ jobs: scan_mode: RAPID build-and-test: - uses: ./.github/workflows/pipeline.yml - with: - deploy-snapshot: true - secrets: inherit \ No newline at end of file + uses: cap-java/cds-feature-attachments/.github/workflows/pipeline.yml@main + secrets: inherit diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 8a7da8016..56d17806c 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -1,113 +1,131 @@ name: Reusable Workflow +permissions: + actions: read + contents: read + packages: read + security-events: write + env: MAVEN_VERSION: '3.9.12' on: workflow_call: - inputs: - deploy-snapshot: - required: true - type: boolean - default: false jobs: - build: - name: Build (Java ${{ matrix.java-version }}) + spotless: + name: Spotless Check runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.sha || github.sha }} + + - name: Set up Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: '17' + distribution: sapmachine + cache: maven + + - name: Set up Maven + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: ${{ env.MAVEN_VERSION }} + + - name: Spotless Check + run: mvn spotless:check -Dspotless.check.skip=false -B -ntp + + tests: + name: Tests (Java ${{ matrix.java-version }}) + runs-on: ubuntu-latest + timeout-minutes: 20 strategy: + fail-fast: false matrix: java-version: [ 17, 21 ] steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - # For internal PRs (same repo), checkout PR head to test actual changes - # For external PRs (forks), checkout base branch for security ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.sha || github.sha }} - - name: Spotless check - run: mvn spotless:check -Dspotless.check.skip=false - - - name: Build - uses: ./.github/actions/build + - name: Set up Java ${{ matrix.java-version }} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: ${{ matrix.java-version }} - maven-version: ${{ env.MAVEN_VERSION }} + distribution: sapmachine + cache: maven - - name: Upload build artifacts - uses: actions/upload-artifact@v7 + - name: Set up Maven + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: - name: build-artifacts-java-${{ matrix.java-version }} - path: | - **/target/*.jar - **/pom.xml - .mvn/ - retention-days: 1 + maven-version: ${{ env.MAVEN_VERSION }} + + - name: Install @sap/cds-dk + run: npm i -g @sap/cds-dk + shell: bash + + - name: Run Tests + run: mvn test -ntp -B -P skip-integration-tests -Dcds.install-node.skip integration-tests: - name: Integration Tests (Java ${{ matrix.java-version }}, ${{ matrix.test-type }}) + name: Integration Tests (Java ${{ matrix.java-version }}, ${{ matrix.auth-method }}, ${{ matrix.hyperscaler }}) runs-on: ubuntu-latest timeout-minutes: 30 - needs: build - env: - ## AWS - AWS_S3_HOST: ${{ secrets.AWS_S3_HOST }} - AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} - AWS_S3_REGION: ${{ secrets.AWS_S3_REGION }} - AWS_S3_ACCESS_KEY_ID: ${{ secrets.AWS_S3_ACCESS_KEY_ID }} - AWS_S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_S3_SECRET_ACCESS_KEY }} - ## Azure - AZURE_CONTAINER_URI: ${{ secrets.AZURE_CONTAINER_URI }} - AZURE_SAS_TOKEN: ${{ secrets.AZURE_SAS_TOKEN }} - ## GCP - GS_BASE_64_ENCODED_PRIVATE_KEY_DATA: ${{ secrets.GS_BASE_64_ENCODED_PRIVATE_KEY_DATA }} - GS_BUCKET: ${{ secrets.GS_BUCKET }} - GS_PROJECT_ID: ${{ secrets.GS_PROJECT_ID }} - ## Malware Scanner (Basic Auth) - MALWARE_SCANNER_URL: ${{ secrets.MALWARE_SCANNER_URL }} - MALWARE_SCANNER_USERNAME: ${{ secrets.MALWARE_SCANNER_USERNAME }} - MALWARE_SCANNER_PASSWORD: ${{ secrets.MALWARE_SCANNER_PASSWORD }} - ## Malware Scanner (mTLS) - MALWARE_SCANNER_MTLS_URI: ${{ secrets.MALWARE_SCANNER_MTLS_URI }} - MALWARE_SCANNER_MTLS_CERTIFICATE: ${{ secrets.MALWARE_SCANNER_MTLS_CERTIFICATE }} - MALWARE_SCANNER_MTLS_KEY: ${{ secrets.MALWARE_SCANNER_MTLS_KEY }} strategy: fail-fast: false matrix: java-version: [ 17, 21 ] - test-type: [ build-version, latest-version, oss ] + auth-method: [ basic, mtls ] + hyperscaler: [ AWS, AZURE, GCP ] steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.sha || github.sha }} - - name: Download build artifacts - uses: actions/download-artifact@v8 + - name: Bind CF Services + uses: cap-java/cds-feature-attachments/.github/actions/cf-bind@main with: - name: build-artifacts-java-${{ matrix.java-version }} + cf-api: ${{ secrets[format('CF_API_{0}', matrix.hyperscaler)] }} + cf-username: ${{ secrets.CF_USERNAME }} + cf-password: ${{ secrets.CF_PASSWORD }} + cf-org: ${{ secrets[format('CF_ORG_{0}', matrix.hyperscaler)] }} + cf-space: ${{ secrets[format('CF_SPACE_{0}', matrix.hyperscaler)] }} + auth-method: ${{ matrix.auth-method }} - name: Integration Tests - uses: ./.github/actions/integration-tests + uses: cap-java/cds-feature-attachments/.github/actions/integration-tests@main with: java-version: ${{ matrix.java-version }} maven-version: ${{ env.MAVEN_VERSION }} - test-type: ${{ matrix.test-type }} sonarqube-scan: name: SonarQube Scan runs-on: ubuntu-latest timeout-minutes: 30 - needs: build steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + + - name: Bind CF Services + uses: cap-java/cds-feature-attachments/.github/actions/cf-bind@main + with: + cf-api: ${{ secrets.CF_API_AWS }} + cf-username: ${{ secrets.CF_USERNAME }} + cf-password: ${{ secrets.CF_PASSWORD }} + cf-org: ${{ secrets.CF_ORG_AWS }} + cf-space: ${{ secrets.CF_SPACE_AWS }} + auth-method: basic + - name: SonarQube Scan - uses: ./.github/actions/scan-with-sonar + uses: cap-java/cds-feature-attachments/.github/actions/scan-with-sonar@main with: java-version: 17 maven-version: ${{ env.MAVEN_VERSION }} @@ -117,7 +135,6 @@ jobs: codeql: name: CodeQL Analysis runs-on: ubuntu-latest - needs: build timeout-minutes: 30 permissions: security-events: write @@ -126,77 +143,12 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@v6 - with: - ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.sha || github.sha }} - - - name: Set up Java - uses: actions/setup-java@v5 - with: - java-version: '17' - distribution: 'sapmachine' - cache: 'maven' - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: java-kotlin - build-mode: manual - - - name: Build Java code - run: mvn clean compile -DskipTests -B -ntp - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:java-kotlin" - - deploy-snapshot: - name: Deploy snapshot to Artifactory - runs-on: ubuntu-latest - timeout-minutes: 30 - if: ${{ inputs.deploy-snapshot == true }} - needs: [build, integration-tests, codeql] - steps: - - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.sha || github.sha }} - - name: Set up Java - uses: actions/setup-java@v5 - with: - java-version: '17' - distribution: 'sapmachine' - cache: 'maven' - server-id: artifactory - server-username: DEPLOYMENT_USER - server-password: DEPLOYMENT_PASS - - - name: Set up Maven ${{ env.MAVEN_VERSION }} - uses: stCarolas/setup-maven@v5 + - name: CodeQL Analysis + uses: cap-java/cds-feature-attachments/.github/actions/scan-with-codeql@main with: + java-version: 17 maven-version: ${{ env.MAVEN_VERSION }} - - - name: Set Dry Run for Pull Request - if: github.event_name == 'pull_request_target' - run: echo "DRY_RUN_PARAM=-DaltDeploymentRepository=local-repo::default::file:./local-repo" >> $GITHUB_ENV - shell: bash - - - name: Get Revision - id: get-revision - run: | - echo "REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout)" >> $GITHUB_OUTPUT - shell: bash - - - name: Print Revision - run: echo "Current revision ${{ steps.get-revision.outputs.REVISION }}" - shell: bash - - - name: Deploy snapshot - if: ${{ endsWith(steps.get-revision.outputs.REVISION, '-SNAPSHOT') }} - run: mvn -B -ntp -fae -pl !integration-tests,!integration-tests/db,!integration-tests/generic,!integration-tests/mtx-local/srv -Dmaven.install.skip=true -Dmaven.test.skip=true -DdeployAtEnd=true deploy - env: - DEPLOYMENT_USER: ${{ secrets.DEPLOYMENT_USER }} - DEPLOYMENT_PASS: ${{ secrets.DEPLOYMENT_PASS }} - shell: bash diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a98b55823..ca8b259c2 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,5 +1,11 @@ name: CI - PR +permissions: + actions: read + contents: read + packages: read + security-events: write + on: workflow_dispatch: pull_request_target: @@ -19,7 +25,5 @@ jobs: build-and-test: needs: requires-approval if: always() && (needs.requires-approval.result == 'success' || needs.requires-approval.result == 'skipped') - uses: ./.github/workflows/pipeline.yml - with: - deploy-snapshot: false - secrets: inherit \ No newline at end of file + uses: cap-java/cds-feature-attachments/.github/workflows/pipeline.yml@main + secrets: inherit diff --git a/.github/workflows/prevent-issue-labeling.yml b/.github/workflows/prevent-issue-labeling.yml index dac7a41b3..6c3503090 100644 --- a/.github/workflows/prevent-issue-labeling.yml +++ b/.github/workflows/prevent-issue-labeling.yml @@ -1,7 +1,6 @@ name: Prevent "New" Label on Issues -permissions: - issues: write +permissions: {} on: issues: @@ -10,6 +9,8 @@ on: jobs: remove_new_label: runs-on: ubuntu-latest + permissions: + issues: write steps: - name: Remove "New" label if applied by non-bot user if: > diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 422008ff2..6e3c93e46 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,7 @@ name: Deploy to Maven Central +permissions: read-all + env: JAVA_VERSION: '17' MAVEN_VERSION: '3.9.12' @@ -9,43 +11,54 @@ on: types: [ "released" ] jobs: + requires-approval: + runs-on: ubuntu-latest + name: "Waiting for release approval" + environment: release-approval + steps: + - name: Approval Step + run: echo "Release has been approved!" + blackduck: + needs: requires-approval name: Blackduck Scan runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Scan With Black Duck - uses: ./.github/actions/scan-with-blackduck + uses: cap-java/cds-feature-attachments/.github/actions/scan-with-blackduck@main with: blackduck_token: ${{ secrets.BLACK_DUCK_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} maven-version: ${{ env.MAVEN_VERSION }} update-version: + needs: requires-approval name: Update Version runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: write steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: token: ${{ secrets.GH_TOKEN }} - name: Update version - uses: ./.github/actions/newrelease + uses: cap-java/cds-feature-attachments/.github/actions/newrelease@main with: java-version: ${{ env.JAVA_VERSION }} maven-version: ${{ env.MAVEN_VERSION }} - name: Upload Changed Artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: root-new-version path: . - include-hidden-files: true retention-days: 1 build: @@ -66,18 +79,18 @@ jobs: GS_PROJECT_ID: ${{ secrets.GS_PROJECT_ID }} steps: - name: Download artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: root-new-version - name: Build - uses: ./.github/actions/build + uses: cap-java/cds-feature-attachments/.github/actions/build@main with: java-version: ${{ env.JAVA_VERSION }} maven-version: ${{ env.MAVEN_VERSION }} - name: Sonar Scan - uses: ./.github/actions/scan-with-sonar + uses: cap-java/cds-feature-attachments/.github/actions/scan-with-sonar@main with: java-version: ${{ env.JAVA_VERSION }} maven-version: ${{ env.MAVEN_VERSION }} @@ -85,10 +98,9 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Upload Changed Artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: root-build - include-hidden-files: true path: . retention-days: 1 @@ -99,12 +111,12 @@ jobs: needs: [blackduck, build] steps: - name: Download artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: root-build - name: Deploy - uses: ./.github/actions/deploy-release + uses: cap-java/cds-feature-attachments/.github/actions/deploy-release@main with: user: ${{ secrets.CENTRAL_REPOSITORY_USER }} password: ${{ secrets.CENTRAL_REPOSITORY_PASS }} @@ -113,6 +125,3 @@ jobs: pgp-passphrase: ${{ secrets.PGP_PASSPHRASE }} revision: ${{ github.event.release.tag_name }} maven-version: ${{ env.MAVEN_VERSION }} - - - name: Echo Status - run: echo "The job status is ${{ job.status }}" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3e33f1df1..4feaa66fb 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,4 +1,7 @@ name: "Close stale issues" + +permissions: {} + on: schedule: - cron: "30 1 * * *" @@ -11,7 +14,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v10 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10 with: close-issue-message: "This issue has been automatically closed due to 2 weeks of inactivity. If you believe this was a mistake, please reopen or comment to continue the discussion." days-before-stale: -1 diff --git a/.pipeline/config.yml b/.pipeline/config.yml deleted file mode 100644 index 111191b00..000000000 --- a/.pipeline/config.yml +++ /dev/null @@ -1,52 +0,0 @@ -steps: - mavenBuild: - verbose: false - verify: false - flatten: true - # https://www.project-piper.io/steps/mavenBuild/#dockerimage - # If empty, Docker is not used and the command is executed directly on the Jenkins system. - dockerImage: '' - - detectExecuteScan: - projectName: 'com.sap.cds.feature.attachments' - groups: - - 'CDSJAVA-OPEN-SOURCE' - serverUrl: 'https://sap.blackducksoftware.com/' - mavenExcludedScopes: [ "provided", "test" ] - failOn: [ 'NONE' ] - versioningModel: "major-minor" - detectTools: [ 'DETECTOR', 'BINARY_SCAN' ] - installArtifacts: false - repository: '/cap-java/cds-feature-attachments' - verbose: true - scanProperties: - - --detect.included.detector.types=MAVEN - - --detect.excluded.directories='**/node_modules,**/*test*,**/localrepo,**/target/site,**/*-site.jar,**/samples/**' - - --detect.maven.excluded.modules=integration-tests,integration-tests/db,integration-tests/generic,integration-tests/mtx-local/srv - - --detect.maven.build.command='-pl com.sap.cds:cds-feature-attachments' - # https://www.project-piper.io/steps/detectExecuteScan/#dockerimage - # If empty, Docker is not used and the command is executed directly on the Jenkins system. - dockerImage: '' - - sonarExecuteScan: - serverUrl: https://sonar.tools.sap - projectKey: cds-feature-attachments - options: - - sonar.qualitygate.wait=true - - sonar.java.source=17 - - sonar.exclusions=**/node_modules/**,**/target/**,**/test/** - - sonar.modules=cds-feature-attachments,cds-feature-attachments-fs,cds-feature-attachments-oss - - sonar.coverage.jacoco.xmlReportPaths=coverage-report/target/site/jacoco-aggregate/jacoco.xml - - sonar.coverage.exclusions=cds-feature-attachments/src/test/**,cds-feature-attachments/src/gen/**,storage-targets/cds-feature-attachments-fs/src/test/**,storage-targets/cds-feature-attachments-oss/src/test/** - - cds-feature-attachments.sonar.projectBaseDir=cds-feature-attachments - - cds-feature-attachments.sonar.sources=src/main/java - - cds-feature-attachments.sonar.tests=src/test/java - - cds-feature-attachments.sonar.java.binaries=target/classes - - cds-feature-attachments-fs.sonar.projectBaseDir=storage-targets/cds-feature-attachments-fs - - cds-feature-attachments-fs.sonar.sources=src/main/java - - cds-feature-attachments-fs.sonar.tests=src/test/java - - cds-feature-attachments-fs.sonar.java.binaries=target/classes - - cds-feature-attachments-oss.sonar.projectBaseDir=storage-targets/cds-feature-attachments-oss - - cds-feature-attachments-oss.sonar.sources=src/main/java - - cds-feature-attachments-oss.sonar.tests=src/test/java - - cds-feature-attachments-oss.sonar.java.binaries=target/classes diff --git a/CLAUDE.md b/CLAUDE.md index 49cf9e133..22734dc62 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,7 +122,6 @@ Defined in `cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-featu All enforced in CI: - **JaCoCo:** 95% minimum (instruction, branch, complexity), 0 missed classes -- **Mutation testing (Pitest):** 90% aggregated threshold on `handler.*` and `service.*` - **SpotBugs:** max effort, includes tests - **PMD:** SAP Cloud SDK rules, excludes generated code and tests - **Spotless:** Google Java Format check diff --git a/cds-feature-attachments/pom.xml b/cds-feature-attachments/pom.xml index 44bec84e4..d41393868 100644 --- a/cds-feature-attachments/pom.xml +++ b/cds-feature-attachments/pom.xml @@ -92,40 +92,6 @@ ${project.artifactId} - - org.pitest - pitest-maven - - - com.sap.cds.feature.attachments.handler.* - com.sap.cds.feature.attachments.service.* - - - CONSTRUCTOR_CALLS - VOID_METHOD_CALLS - NON_VOID_METHOD_CALLS - REMOVE_CONDITIONALS_ORDER_ELSE - CONDITIONALS_BOUNDARY - EMPTY_RETURNS - NEGATE_CONDITIONALS - REMOVE_CONDITIONALS_EQUAL_IF - REMOVE_CONDITIONALS_EQUAL_ELSE - REMOVE_CONDITIONALS_ORDER_IF - REMOVE_CONDITIONALS_ORDER_ELSE - - 95 - 90 - - - - - org.pitest - pitest-junit5-plugin - 1.2.3 - - - - maven-clean-plugin @@ -314,4 +280,31 @@ + + + integration-tests + + + + maven-failsafe-plugin + + + **/*IT.java + + + + + integration-tests + + integration-test + verify + + + + + + + + + diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/service/malware/client/MalwareScanClientIT.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/service/malware/client/MalwareScanClientIT.java index e29302c74..b1d247a84 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/service/malware/client/MalwareScanClientIT.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/service/malware/client/MalwareScanClientIT.java @@ -4,15 +4,13 @@ package com.sap.cds.feature.attachments.service.malware.client; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import com.sap.cds.services.environment.CdsProperties.ConnectionPool; +import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; import com.sap.cloud.environment.servicebinding.api.ServiceBinding; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Map; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; @@ -20,9 +18,6 @@ import org.junit.jupiter.api.TestInstance; class MalwareScanClientIT { - // The tests in this class are intended to run against a real Malware Scanner instance. - // They require valid credentials set up in the environment. - // Basic auth and mTLS tests skip independently when their credentials are missing. private static final String EICAR_TEST_STRING = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"; @@ -30,15 +25,16 @@ class MalwareScanClientIT { private static final ConnectionPool CONNECTION_POOL = new ConnectionPool(Duration.ofSeconds(120), 20, 20); - private static MalwareScanClient buildClient(Map creds) { - ServiceBinding binding = mock(ServiceBinding.class); - when(binding.getCredentials()).thenReturn(creds); - HttpClientProvider clientProvider = new MalwareScanClientProvider(binding, CONNECTION_POOL); - return new DefaultMalwareScanClient(clientProvider); + private static ServiceBinding getMalwareScannerBinding() { + return DefaultServiceBindingAccessor.getInstance().getServiceBindings().stream() + .filter(b -> b.getServiceName().map("malware-scanner"::equals).orElse(false)) + .findFirst() + .orElse(null); } - private static String normalizePem(String pem) { - return pem.replace("\\n", "\n"); + private static MalwareScanClient buildClient(ServiceBinding binding) { + HttpClientProvider clientProvider = new MalwareScanClientProvider(binding, CONNECTION_POOL); + return new DefaultMalwareScanClient(clientProvider); } @Nested @@ -49,18 +45,12 @@ class BasicAuth { @BeforeAll void setUp() { - String url = System.getenv("MALWARE_SCANNER_URL"); - String username = System.getenv("MALWARE_SCANNER_USERNAME"); - String password = System.getenv("MALWARE_SCANNER_PASSWORD"); - - if (url == null || username == null || password == null) { - client = null; - } else { - client = buildClient(Map.of("url", url, "username", username, "password", password)); + ServiceBinding binding = getMalwareScannerBinding(); + if (binding != null && binding.getCredentials().containsKey("username")) { + client = buildClient(binding); } - Assumptions.assumeTrue( - client != null, "Basic auth malware scanner credentials not available — skipping tests"); + client != null, "Basic auth malware scanner binding not available — skipping tests"); } @Test @@ -90,26 +80,12 @@ class Mtls { @BeforeAll void setUp() { - String uri = System.getenv("MALWARE_SCANNER_MTLS_URI"); - String certificate = System.getenv("MALWARE_SCANNER_MTLS_CERTIFICATE"); - String key = System.getenv("MALWARE_SCANNER_MTLS_KEY"); - - if (uri == null || certificate == null || key == null) { - client = null; - } else { - client = - buildClient( - Map.of( - "uri", - uri, - "certificate", - normalizePem(certificate), - "key", - normalizePem(key))); + ServiceBinding binding = getMalwareScannerBinding(); + if (binding != null && binding.getCredentials().containsKey("certificate")) { + client = buildClient(binding); } - Assumptions.assumeTrue( - client != null, "mTLS malware scanner credentials not available — skipping tests"); + client != null, "mTLS malware scanner binding not available — skipping tests"); } @Test diff --git a/doc/Design.md b/doc/Design.md index 1945024c5..6777dde22 100644 --- a/doc/Design.md +++ b/doc/Design.md @@ -51,7 +51,6 @@ - [Texts](#texts) - [Tests](#tests) - [Unit Tests](#unit-tests) - - [Mutation Tests](#mutation-tests) - [Integration Tests](#integration-tests) - [Quality Tools](#quality-tools) @@ -93,21 +92,19 @@ In folder `.github/workflows` are the GitHub Actions defined. The following tabl | File Name | Description | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `pr.yml` | Builds and tests pull requests for Java 17 and 21. Requires approval for external forks. Each pull request needs green runs from this workflow to be merged. | -| `main.yml` | Builds, tests, and deploys snapshots when commits are merged to main. Runs unit tests, integration tests, and mutation tests for Java 17 and 21. | +| `main.yml` | Builds and tests when commits are merged to main. Runs unit tests and integration tests for Java 17 and 21. | | `release.yml` | Triggered on GitHub releases. Updates version, runs BlackDuck scan, builds, tests, and deploys to Maven Central. See also [Build and Deploy](#build-and-deploy). | | `pipeline.yml` | Reusable workflow containing shared build, test, integration test, SonarQube scan, CodeQL analysis, and snapshot deployment logic. Called by `pr.yml` and `main.yml`. | ### Build Action The build step is implemented in action `.github/actions/build/action.yml` which is used in the workflows via `pipeline.yml`. -As the build action does not only run a build of the project, but also the mutation tests, this action is used in all -the mentioned workflows. Additional reusable actions are defined in `.github/actions/`: | Action | Description | | --------------------- | ----------------------------------------------------------- | -| `build` | Builds the project and runs unit/mutation tests | +| `build` | Builds the project and runs unit tests | | `integration-tests` | Runs integration tests (build-version, latest-version, oss) | | `deploy-release` | Deploys release artifacts to Maven Central | | `newrelease` | Updates version in pom.xml for new releases | @@ -142,7 +139,7 @@ The following steps are executed in the workflow: 1. Update the version in the `pom.xml` files. The tag used in the release is read and git commands are used to update the property `revision` in the parent `pom.xml` file. -2. Build the project and run all unit, integration and mutation tests. Here a reuse action is used which is also +2. Build the project and run all unit and integration tests. Here a reuse action is used which is also executed in the main and pull request build. 3. Deploy the project to maven or artifactory. The deployment is done with the maven command `mvn deploy`. The deployment is done to the repository defined in the `pom.xml` file. So only project parts which have defined the @@ -660,18 +657,6 @@ The following settings are used for this plugin: | Complexity Coverage | 95% | | Class Missed Count | 0 | -#### Mutation Tests - -In addition to this plugin, also mutation tests are executed during the build of the project in the GitHub Actions. -To run the mutation tests the plugin `pitest-maven` is included in the same pom. - -Several mutators are maintained in the plugin and the following settings are used: - -| Setting | Value | -| ----------------------------- | ----- | -| Coverage Threshold | 95% | -| Aggregated Mutation Threshold | 90% | - ### Integration Tests Spring Boot tests are implemented in the `integration-tests` folder. @@ -746,7 +731,6 @@ The following quality tools are used in the project to ensure the quality of the | Spotbugs | Defined in the root `pom.xml` | Static Code check for Java code working in the bytecode. | | PMD/CPD | Defined in the root `pom.xml` | Static Code check for Java code working on the source code. CPD checks the coding for duplications. | | Maven Enforcer Plugin | Defined in the root `pom.xml` | Checks if there are dependencies declared twice. | -| Mutation Tests | Defined in `cds-feature-attachments/pom.xml` | See section [mutation tests](#mutation-tests). | | Jacoco | Defined in `cds-feature-attachments/pom.xml` | See section [unit tests](#unit-tests). | | Dependabot | Config is defined in the `.github/dependabot.yml` | Checks for new versions of dependencies. | | CodeQL | Defined in `pipeline.yml` | Checks for vulnerabilities in the coding. Executed as part of the CI pipeline. | diff --git a/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/AwsMtxOssStorageTest.java b/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/AwsMtxOssStorageTest.java index 068504f6e..c2e8d5710 100644 --- a/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/AwsMtxOssStorageTest.java +++ b/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/AwsMtxOssStorageTest.java @@ -3,46 +3,22 @@ */ package com.sap.cds.feature.attachments.integrationtests.mt.oss; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - +import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; import com.sap.cloud.environment.servicebinding.api.ServiceBinding; -import java.util.HashMap; /** * Runs the multitenancy OSS storage integration tests against a real AWS S3 instance. Skipped - * automatically if the required environment variables are not set. - * - *

Required environment variables: {@code AWS_S3_HOST}, {@code AWS_S3_BUCKET}, {@code - * AWS_S3_REGION}, {@code AWS_S3_ACCESS_KEY_ID}, {@code AWS_S3_SECRET_ACCESS_KEY}. + * automatically if no objectstore binding with AWS credentials is available in VCAP_SERVICES. */ class AwsMtxOssStorageTest extends AbstractMtxOssStorageTest { @Override protected ServiceBinding getServiceBinding() { - String host = System.getenv("AWS_S3_HOST"); - String bucket = System.getenv("AWS_S3_BUCKET"); - String region = System.getenv("AWS_S3_REGION"); - String accessKeyId = System.getenv("AWS_S3_ACCESS_KEY_ID"); - String secretAccessKey = System.getenv("AWS_S3_SECRET_ACCESS_KEY"); - - if (host == null - || bucket == null - || region == null - || accessKeyId == null - || secretAccessKey == null) { - return null; - } - - ServiceBinding binding = mock(ServiceBinding.class); - HashMap creds = new HashMap<>(); - creds.put("host", host); - creds.put("bucket", bucket); - creds.put("region", region); - creds.put("access_key_id", accessKeyId); - creds.put("secret_access_key", secretAccessKey); - when(binding.getCredentials()).thenReturn(creds); - return binding; + return DefaultServiceBindingAccessor.getInstance().getServiceBindings().stream() + .filter(b -> b.getServiceName().map("objectstore"::equals).orElse(false)) + .filter(b -> b.getCredentials().containsKey("access_key_id")) + .findFirst() + .orElse(null); } @Override diff --git a/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/AzureMtxOssStorageTest.java b/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/AzureMtxOssStorageTest.java index 1ebe1a354..f806406be 100644 --- a/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/AzureMtxOssStorageTest.java +++ b/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/AzureMtxOssStorageTest.java @@ -3,35 +3,23 @@ */ package com.sap.cds.feature.attachments.integrationtests.mt.oss; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - +import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; import com.sap.cloud.environment.servicebinding.api.ServiceBinding; -import java.util.HashMap; /** * Runs the multitenancy OSS storage integration tests against a real Azure Blob Storage instance. - * Skipped automatically if the required environment variables are not set. - * - *

Required environment variables: {@code AZURE_CONTAINER_URI}, {@code AZURE_SAS_TOKEN}. + * Skipped automatically if no objectstore binding with Azure credentials is available in + * VCAP_SERVICES. */ class AzureMtxOssStorageTest extends AbstractMtxOssStorageTest { @Override protected ServiceBinding getServiceBinding() { - String containerUri = System.getenv("AZURE_CONTAINER_URI"); - String sasToken = System.getenv("AZURE_SAS_TOKEN"); - - if (containerUri == null || sasToken == null) { - return null; - } - - ServiceBinding binding = mock(ServiceBinding.class); - HashMap creds = new HashMap<>(); - creds.put("container_uri", containerUri); - creds.put("sas_token", sasToken); - when(binding.getCredentials()).thenReturn(creds); - return binding; + return DefaultServiceBindingAccessor.getInstance().getServiceBindings().stream() + .filter(b -> b.getServiceName().map("objectstore"::equals).orElse(false)) + .filter(b -> b.getCredentials().containsKey("container_uri")) + .findFirst() + .orElse(null); } @Override diff --git a/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/GcpMtxOssStorageTest.java b/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/GcpMtxOssStorageTest.java index 017248d77..9476ff780 100644 --- a/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/GcpMtxOssStorageTest.java +++ b/integration-tests/mtx-local/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/mt/oss/GcpMtxOssStorageTest.java @@ -3,38 +3,23 @@ */ package com.sap.cds.feature.attachments.integrationtests.mt.oss; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - +import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; import com.sap.cloud.environment.servicebinding.api.ServiceBinding; -import java.util.HashMap; /** * Runs the multitenancy OSS storage integration tests against a real Google Cloud Storage instance. - * Skipped automatically if the required environment variables are not set. - * - *

Required environment variables: {@code GS_BUCKET}, {@code GS_PROJECT_ID}, {@code - * GS_BASE_64_ENCODED_PRIVATE_KEY_DATA}. + * Skipped automatically if no objectstore binding with GCP credentials is available in + * VCAP_SERVICES. */ class GcpMtxOssStorageTest extends AbstractMtxOssStorageTest { @Override protected ServiceBinding getServiceBinding() { - String bucket = System.getenv("GS_BUCKET"); - String projectId = System.getenv("GS_PROJECT_ID"); - String base64EncodedPrivateKeyData = System.getenv("GS_BASE_64_ENCODED_PRIVATE_KEY_DATA"); - - if (bucket == null || projectId == null || base64EncodedPrivateKeyData == null) { - return null; - } - - ServiceBinding binding = mock(ServiceBinding.class); - HashMap creds = new HashMap<>(); - creds.put("bucket", bucket); - creds.put("projectId", projectId); - creds.put("base64EncodedPrivateKeyData", base64EncodedPrivateKeyData); - when(binding.getCredentials()).thenReturn(creds); - return binding; + return DefaultServiceBindingAccessor.getInstance().getServiceBindings().stream() + .filter(b -> b.getServiceName().map("objectstore"::equals).orElse(false)) + .filter(b -> b.getCredentials().containsKey("base64EncodedPrivateKeyData")) + .findFirst() + .orElse(null); } @Override diff --git a/pom.xml b/pom.xml index 2e775ac3b..78fc4a81b 100644 --- a/pom.xml +++ b/pom.xml @@ -69,15 +69,9 @@ 2.42.33 0.44.0 - - - 4.6.1 - - 9.6.1 - - 4.8.0 - 9.7.2 + 4.9.0 + 9.9.0 true @@ -238,11 +232,6 @@ jacoco-maven-plugin 0.8.14 - - org.pitest - pitest-maven - 1.23.0 - com.github.spotbugs spotbugs-maven-plugin diff --git a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AWSClientIT.java b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AWSClientIT.java index 40aef4d38..d40a3c354 100644 --- a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AWSClientIT.java +++ b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AWSClientIT.java @@ -3,51 +3,30 @@ */ package com.sap.cds.feature.attachments.oss.client; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import com.sap.cds.feature.attachments.oss.handler.OSSAttachmentsServiceHandlerTestUtils; +import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; import com.sap.cloud.environment.servicebinding.api.ServiceBinding; -import java.util.HashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.jupiter.api.Test; class AWSClientIT { - // The tests in this class are intended to run against a real AWS Storage instance. - // They require a valid ServiceBinding with credentials set up in the environment. @Test void testCreateReadDeleteAttachmentFlowAWS() { - ServiceBinding binding = getRealServiceBindingAWS(); + ServiceBinding binding = getObjectStoreBinding(); + assumeTrue(binding != null, "No AWS objectstore binding available"); ExecutorService executor = Executors.newCachedThreadPool(); OSSAttachmentsServiceHandlerTestUtils.testCreateReadDeleteAttachmentFlow(binding, executor); } - private ServiceBinding getRealServiceBindingAWS() { - // Read environment variables - String host = System.getenv("AWS_S3_HOST"); - String bucket = System.getenv("AWS_S3_BUCKET"); - String region = System.getenv("AWS_S3_REGION"); - String accessKeyId = System.getenv("AWS_S3_ACCESS_KEY_ID"); - String secretAccessKey = System.getenv("AWS_S3_SECRET_ACCESS_KEY"); - - // Return null if any are missing - if (host == null - || bucket == null - || region == null - || accessKeyId == null - || secretAccessKey == null) { - return null; - } - - ServiceBinding binding = mock(ServiceBinding.class); - HashMap creds = new HashMap<>(); - creds.put("host", host); - creds.put("bucket", bucket); - creds.put("region", region); - creds.put("access_key_id", accessKeyId); - creds.put("secret_access_key", secretAccessKey); - when(binding.getCredentials()).thenReturn(creds); - return binding; + private ServiceBinding getObjectStoreBinding() { + return DefaultServiceBindingAccessor.getInstance().getServiceBindings().stream() + .filter(b -> b.getServiceName().map("objectstore"::equals).orElse(false)) + .filter(b -> b.getCredentials().containsKey("access_key_id")) + .findFirst() + .orElse(null); } } diff --git a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AzureClientIT.java b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AzureClientIT.java index 387e6b683..fc16a1fbe 100644 --- a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AzureClientIT.java +++ b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AzureClientIT.java @@ -3,41 +3,30 @@ */ package com.sap.cds.feature.attachments.oss.client; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import com.sap.cds.feature.attachments.oss.handler.OSSAttachmentsServiceHandlerTestUtils; +import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; import com.sap.cloud.environment.servicebinding.api.ServiceBinding; -import java.util.HashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.jupiter.api.Test; class AzureClientIT { - // The tests in this class are intended to run against a real Azure instance. - // They require a valid ServiceBinding with credentials set up in the environment. @Test void testCreateReadDeleteAttachmentFlowAzure() { - ServiceBinding binding = getRealServiceBindingAzure(); + ServiceBinding binding = getObjectStoreBinding(); + assumeTrue(binding != null, "No Azure objectstore binding available"); ExecutorService executor = Executors.newCachedThreadPool(); - OSSAttachmentsServiceHandlerTestUtils.testCreateReadDeleteAttachmentFlow(binding, executor); } - private ServiceBinding getRealServiceBindingAzure() { - // Read environment variables - String containerUri = System.getenv("AZURE_CONTAINER_URI"); - String sasToken = System.getenv("AZURE_SAS_TOKEN"); - // Return null if any are missing - if (containerUri == null || sasToken == null) { - return null; - } - - ServiceBinding binding = mock(ServiceBinding.class); - HashMap creds = new HashMap<>(); - creds.put("container_uri", containerUri); - creds.put("sas_token", sasToken); - when(binding.getCredentials()).thenReturn(creds); - return binding; + private ServiceBinding getObjectStoreBinding() { + return DefaultServiceBindingAccessor.getInstance().getServiceBindings().stream() + .filter(b -> b.getServiceName().map("objectstore"::equals).orElse(false)) + .filter(b -> b.getCredentials().containsKey("container_uri")) + .findFirst() + .orElse(null); } } diff --git a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/GoogleClientIT.java b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/GoogleClientIT.java index 759c49673..6230a58bb 100644 --- a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/GoogleClientIT.java +++ b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/GoogleClientIT.java @@ -3,44 +3,30 @@ */ package com.sap.cds.feature.attachments.oss.client; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import com.sap.cds.feature.attachments.oss.handler.OSSAttachmentsServiceHandlerTestUtils; +import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; import com.sap.cloud.environment.servicebinding.api.ServiceBinding; -import java.util.HashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.jupiter.api.Test; class GoogleClientIT { - // The tests in this class are intended to run against a real Google Cloud Storage instance. - // They require a valid ServiceBinding with credentials set up in the environment. @Test void testCreateReadDeleteAttachmentFlowGoogle() { - ServiceBinding binding = getRealServiceBindingGoogle(); + ServiceBinding binding = getObjectStoreBinding(); + assumeTrue(binding != null, "No Google Cloud Storage objectstore binding available"); ExecutorService executor = Executors.newCachedThreadPool(); - OSSAttachmentsServiceHandlerTestUtils.testCreateReadDeleteAttachmentFlow(binding, executor); } - private ServiceBinding getRealServiceBindingGoogle() { - // Read environment variables - String bucket = System.getenv("GS_BUCKET"); - String projectId = System.getenv("GS_PROJECT_ID"); - String base64EncodedPrivateKeyData = System.getenv("GS_BASE_64_ENCODED_PRIVATE_KEY_DATA"); - - // Return null if any are missing - if (bucket == null || projectId == null || base64EncodedPrivateKeyData == null) { - return null; - } - - ServiceBinding binding = mock(ServiceBinding.class); - HashMap creds = new HashMap<>(); - creds.put("bucket", bucket); - creds.put("projectId", projectId); - creds.put("base64EncodedPrivateKeyData", base64EncodedPrivateKeyData); - when(binding.getCredentials()).thenReturn(creds); - return binding; + private ServiceBinding getObjectStoreBinding() { + return DefaultServiceBindingAccessor.getInstance().getServiceBindings().stream() + .filter(b -> b.getServiceName().map("objectstore"::equals).orElse(false)) + .filter(b -> b.getCredentials().containsKey("base64EncodedPrivateKeyData")) + .findFirst() + .orElse(null); } }