feat(exceptions): add AccessDeniedException handler, and Unit tests for GlobalExceptionHandler #10720
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Test Workflow | |
| on: | |
| workflow_dispatch: | |
| # push: | |
| # branches: ["main"] | |
| pull_request: | |
| branches: ["main"] | |
| # cancel in-progress jobs if a new job is triggered | |
| # This is useful to avoid running multiple builds for the same branch if a new commit is pushed | |
| # or a pull request is updated. | |
| # It helps to save resources and time by ensuring that only the latest commit is built and tested | |
| # This is particularly useful for long-running jobs that may take a while to complete. | |
| # The `group` is set to a combination of the workflow name, event name, and branch name. | |
| # This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of | |
| # in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| files-changed: | |
| name: detect what files changed | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 3 | |
| # Map a step output to a job output | |
| outputs: | |
| build: ${{ steps.changes.outputs.build }} | |
| app: ${{ steps.changes.outputs.app }} | |
| project: ${{ steps.changes.outputs.project }} | |
| openapi: ${{ steps.changes.outputs.openapi }} | |
| docker: ${{ steps.changes.outputs.docker }} | |
| steps: | |
| - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Check for file changes | |
| uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 | |
| id: changes | |
| with: | |
| filters: ".github/config/.files.yaml" | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| security-events: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| jdk-version: [17, 21] | |
| spring-security: [true, false] | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout repository | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Set up JDK ${{ matrix.jdk-version }} | |
| uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 | |
| with: | |
| java-version: ${{ matrix.jdk-version }} | |
| distribution: "temurin" | |
| cache: gradle | |
| - name: Build with Gradle and spring security ${{ matrix.spring-security }} | |
| run: ./gradlew clean build -x spotlessApply -x spotlessCheck -x sonarqube | |
| env: | |
| DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }} | |
| - name: Check Test Reports Exist | |
| id: check-reports | |
| if: always() | |
| run: | | |
| declare -a dirs=( | |
| "app/core/build/reports/tests/" | |
| "app/core/build/test-results/" | |
| "app/common/build/reports/tests/" | |
| "app/common/build/test-results/" | |
| "app/proprietary/build/reports/tests/" | |
| "app/proprietary/build/test-results/" | |
| ) | |
| missing_reports=() | |
| for dir in "${dirs[@]}"; do | |
| if [ ! -d "$dir" ]; then | |
| missing_reports+=("$dir") | |
| fi | |
| done | |
| if [ ${#missing_reports[@]} -gt 0 ]; then | |
| echo "ERROR: The following required test report directories are missing:" | |
| printf '%s\n' "${missing_reports[@]}" | |
| echo "reports-present=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "All required test report directories are present" | |
| echo "reports-present=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Upload Test Reports | |
| if: always() && steps.check-reports.outputs.reports-present == 'true' | |
| uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | |
| with: | |
| name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }} | |
| path: | | |
| app/core/build/reports/jacoco/test | |
| app/core/build/reports/tests/ | |
| app/core/build/test-results/ | |
| app/core/build/reports/problems/ | |
| app/common/build/reports/tests/ | |
| app/common/build/test-results/ | |
| app/common/build/reports/jacoco/test | |
| app/common/build/reports/problems/ | |
| app/proprietary/build/reports/tests/ | |
| app/proprietary/build/test-results/ | |
| app/proprietary/build/reports/jacoco/test | |
| app/proprietary/build/reports/problems/ | |
| build/reports/problems/ | |
| retention-days: 3 | |
| if-no-files-found: warn | |
| - name: Add coverage to PR with spring security ${{ matrix.spring-security }} and JDK ${{ matrix.jdk-version }} | |
| if: steps.check-reports.outputs.reports-present == 'true' | |
| id: jacoco | |
| uses: madrapps/jacoco-report@50d3aff4548aa991e6753342d9ba291084e63848 # v1.7.2 | |
| with: | |
| paths: | | |
| ${{ github.workspace }}/**/build/reports/jacoco/test/jacocoTestReport.xml | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| min-coverage-overall: 10 | |
| min-coverage-changed-files: 0 | |
| comment-type: summary | |
| check-generateOpenApiDocs: | |
| if: needs.files-changed.outputs.openapi == 'true' | |
| needs: [files-changed, build] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout repository | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| cache: gradle | |
| - name: Generate OpenAPI documentation | |
| run: ./gradlew :stirling-pdf:generateOpenApiDocs | |
| env: | |
| DISABLE_ADDITIONAL_FEATURES: true | |
| - name: Upload OpenAPI Documentation | |
| uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | |
| with: | |
| name: openapi-docs | |
| path: ./SwaggerDoc.json | |
| check-licence: | |
| if: needs.files-changed.outputs.build == 'true' | |
| needs: [files-changed, build] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout repository | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| cache: gradle | |
| - name: Check licenses for compatibility | |
| run: ./gradlew clean checkLicense | |
| env: | |
| DISABLE_ADDITIONAL_FEATURES: false | |
| STIRLING_PDF_DESKTOP_UI: true | |
| - name: FAILED - Check licenses for compatibility | |
| if: failure() | |
| uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | |
| with: | |
| name: dependencies-without-allowed-license.json | |
| path: | | |
| build/reports/dependency-license/dependencies-without-allowed-license.json | |
| retention-days: 3 | |
| docker-compose-tests: | |
| if: | | |
| needs.files-changed.outputs.project == 'true' && | |
| ( | |
| needs.files-changed.outputs.docker != 'true' || | |
| needs.test-build-docker-images.result == 'success' || | |
| needs.test-build-docker-images.result == 'skipped' | |
| ) | |
| needs: [files-changed, test-build-docker-images] | |
| # if: github.event_name == 'push' && github.ref == 'refs/heads/main' || | |
| # (github.event_name == 'pull_request' && | |
| # contains(github.event.pull_request.labels.*.name, 'licenses') == false && | |
| # ( | |
| # contains(github.event.pull_request.labels.*.name, 'Front End') || | |
| # contains(github.event.pull_request.labels.*.name, 'Java') || | |
| # contains(github.event.pull_request.labels.*.name, 'Back End') || | |
| # contains(github.event.pull_request.labels.*.name, 'Security') || | |
| # contains(github.event.pull_request.labels.*.name, 'API') || | |
| # contains(github.event.pull_request.labels.*.name, 'Docker') || | |
| # contains(github.event.pull_request.labels.*.name, 'Test') | |
| # ) | |
| # ) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout Repository | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Set up Java 17 | |
| uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| cache: gradle | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 | |
| - name: Install Docker Compose | |
| run: | | |
| sudo curl -SL "https://github.com/docker/compose/releases/download/v2.40.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose | |
| sudo chmod +x /usr/local/bin/docker-compose | |
| - name: Set up Python | |
| uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 | |
| with: | |
| python-version: "3.12" | |
| cache: "pip" # caching pip dependencies | |
| cache-dependency-path: ./testing/cucumber/requirements.txt | |
| - name: Pip requirements | |
| run: | | |
| pip install --require-hashes -r ./testing/cucumber/requirements.txt | |
| - name: Run Docker Compose Tests | |
| run: | | |
| chmod +x ./testing/test_webpages.sh | |
| chmod +x ./testing/test.sh | |
| chmod +x ./testing/test_disabledEndpoints.sh | |
| ./testing/test.sh | |
| test-build-docker-images: | |
| if: github.event_name == 'pull_request' && needs.files-changed.outputs.docker == 'true' | |
| needs: [files-changed, build] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| docker: | |
| - name: "Dockerfile.ultra-lite" | |
| tag: "ultra-lite" | |
| - name: "Dockerfile.fat" | |
| tag: "fat" | |
| - name: "Dockerfile" | |
| tag: "latest" | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout Repository | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| cache: gradle | |
| - name: Build application | |
| run: ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube | |
| env: | |
| DISABLE_ADDITIONAL_FEATURES: true | |
| STIRLING_PDF_DESKTOP_UI: false | |
| # - name: Free disk space on runner | |
| # run: | | |
| # echo "Disk space before cleanup:" && df -h | |
| # sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/boost | |
| # docker system prune -af || true | |
| # echo "Disk space after cleanup:" && df -h | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 | |
| with: | |
| platforms: linux/amd64,linux/arm64/v8 | |
| - name: Set up Docker Buildx | |
| id: buildx | |
| uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 | |
| with: | |
| platforms: linux/amd64,linux/arm64/v8 | |
| - name: Prepare branch tag | |
| id: branch_tag | |
| shell: bash | |
| run: | | |
| BRANCH_SOURCE="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" | |
| BRANCH_LOWER=$(echo "$BRANCH_SOURCE" | tr '[:upper:]' '[:lower:]') | |
| SAFE_BRANCH=$(echo "$BRANCH_LOWER" | sed 's/[^a-z0-9_.-]/-/g' | sed 's/^-\+//' | sed 's/-\+$//' | sed 's/--\+/-/g') | |
| if [ -z "$SAFE_BRANCH" ]; then | |
| SAFE_BRANCH="branch" | |
| fi | |
| SHORT_SHA=$(echo "${GITHUB_SHA:-${{ github.sha }}}" | cut -c1-8) | |
| echo "safe_branch=$SAFE_BRANCH" >> "$GITHUB_OUTPUT" | |
| echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT" | |
| - name: Convert repository owner to lowercase | |
| id: repoowner | |
| run: echo "lowercase=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 | |
| with: | |
| images: | | |
| # ${{ secrets.DOCKER_HUB_USERNAME }}/stirling-pdf-test | |
| ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test | |
| flavor: | | |
| latest=false | |
| tags: | | |
| type=raw,value=${{ matrix.docker.tag }},enable=true | |
| # type=raw,value=${{ matrix.docker.tag }}-${{ steps.branch_tag.outputs.safe_branch }},enable=true | |
| # type=raw,value=${{ matrix.docker.tag }}-${{ steps.branch_tag.outputs.safe_branch }}-${{ steps.branch_tag.outputs.short_sha }},enable=true | |
| labels: | | |
| org.opencontainers.image.title=Stirling-PDF Test | |
| org.opencontainers.image.description=CI test image for Stirling-PDF | |
| org.opencontainers.image.url=https://www.stirlingpdf.com | |
| org.opencontainers.image.documentation=https://docs.stirlingpdf.com | |
| org.opencontainers.image.authors=Stirling-Tools | |
| org.opencontainers.image.licenses=MIT | |
| org.opencontainers.image.version=${{ matrix.docker.tag }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.source=${{ github.repository }} | |
| maintainer=Stirling-Tools | |
| - name: Choose primary tag for tests | |
| id: testtag | |
| shell: bash | |
| run: | | |
| IMAGE="ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test" | |
| VARIANT="${{ matrix.docker.tag }}" | |
| BRANCH="${{ steps.branch_tag.outputs.safe_branch }}" | |
| SHA_SHORT="${{ steps.branch_tag.outputs.short_sha }}" | |
| CANDIDATE="$IMAGE:$VARIANT-$BRANCH-$SHA_SHORT" | |
| SECONDARY="$IMAGE:$VARIANT-$BRANCH" | |
| ALL_TAGS="$(echo '${{ steps.meta.outputs.tags }}' | tr ' ' '\n')" | |
| if echo "$ALL_TAGS" | grep -qx "$CANDIDATE"; then | |
| SELECTED="$CANDIDATE" | |
| elif echo "$ALL_TAGS" | grep -qx "$SECONDARY"; then | |
| SELECTED="$SECONDARY" | |
| else | |
| SELECTED="$(echo "$ALL_TAGS" | head -n1)" | |
| fi | |
| echo "tag=$SELECTED" >> $GITHUB_OUTPUT | |
| echo "Using test tag: $SELECTED" | |
| # - name: Log in to Docker Hub | |
| # uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 | |
| # with: | |
| # username: ${{ secrets.DOCKER_HUB_USERNAME }} | |
| # password: ${{ secrets.DOCKER_HUB_API }} | |
| # - name: Log in to GitHub Container Registry | |
| # uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 | |
| # with: | |
| # registry: ghcr.io | |
| # username: ${{ github.actor }} | |
| # password: ${{ github.token }} | |
| - name: Build and push amd64 image | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 | |
| with: | |
| builder: ${{ steps.buildx.outputs.name }} | |
| context: . | |
| file: ./${{ matrix.docker.name }} | |
| push: false | |
| load: true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| tags: ${{ steps.meta.outputs.tags }} # ALLE Tags publishen | |
| labels: ${{ steps.meta.outputs.labels }} | |
| platforms: linux/amd64 | |
| provenance: false | |
| sbom: false | |
| - name: Show amd64 image size | |
| run: | | |
| IMAGE_TAG="${{ steps.testtag.outputs.tag }}" | |
| echo "Inspecting image: ${IMAGE_TAG}" | |
| SIZE=$(docker image inspect "${IMAGE_TAG}" --format='{{.Size}}') | |
| FORMATTED=$(numfmt --to=iec --suffix=B "${SIZE}") | |
| echo "Image size (amd64): ${FORMATTED}" | |
| - name: Start amd64 image for 2 minutes | |
| run: | | |
| IMAGE_TAG="${{ steps.testtag.outputs.tag }}" | |
| CONTAINER_NAME="stirling-pdf-test-${{ matrix.docker.tag }}-amd64" | |
| echo "Starting container ${CONTAINER_NAME} from ${IMAGE_TAG}" | |
| docker run -d --name "${CONTAINER_NAME}" "${IMAGE_TAG}" | |
| echo "Waiting up to 2 minutes..." | |
| sleep 120 || true | |
| echo "===== Logs for ${CONTAINER_NAME} =====" | |
| docker logs "${CONTAINER_NAME}" || true | |
| echo "Stopping container ${CONTAINER_NAME} after 2 minutes" | |
| docker stop "${CONTAINER_NAME}" || true | |
| docker rm "${CONTAINER_NAME}" || true | |
| - name: Prune amd64 image and cache | |
| if: always() | |
| run: | | |
| docker image rm -f ${{ steps.testtag.outputs.tag }} || true | |
| docker builder prune --force || true | |
| - name: Build and push arm64 image | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 | |
| with: | |
| builder: ${{ steps.buildx.outputs.name }} | |
| context: . | |
| file: ./${{ matrix.docker.name }} | |
| push: false | |
| load: true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| tags: ${{ steps.meta.outputs.tags }} # ALLE Tags publishen | |
| labels: ${{ steps.meta.outputs.labels }} | |
| platforms: linux/arm64/v8 | |
| provenance: false | |
| sbom: false | |
| - name: Show arm64 image size | |
| run: | | |
| IMAGE_TAG="${{ steps.testtag.outputs.tag }}" | |
| echo "Inspecting image: ${IMAGE_TAG}" | |
| SIZE=$(docker image inspect "${IMAGE_TAG}" --format='{{.Size}}') | |
| FORMATTED=$(numfmt --to=iec --suffix=B "${SIZE}") | |
| echo "Image size (arm64): ${FORMATTED}" | |
| - name: Start arm64 image for 2 minutes | |
| run: | | |
| IMAGE_TAG="${{ steps.testtag.outputs.tag }}" | |
| CONTAINER_NAME="stirling-pdf-test-${{ matrix.docker.tag }}-arm64" | |
| echo "Starting container ${CONTAINER_NAME} from ${IMAGE_TAG}" | |
| docker run -d --name "${CONTAINER_NAME}" "${IMAGE_TAG}" | |
| echo "Waiting up to 2 minutes..." | |
| sleep 120 || true | |
| echo "===== Logs for ${CONTAINER_NAME} =====" | |
| docker logs "${CONTAINER_NAME}" || true | |
| echo "Stopping container ${CONTAINER_NAME} after 2 minutes" | |
| docker stop "${CONTAINER_NAME}" || true | |
| docker rm "${CONTAINER_NAME}" || true | |
| - name: Cleanup arm64 image and cache | |
| if: always() | |
| run: | | |
| docker image rm -f ${{ steps.testtag.outputs.tag }} || true | |
| docker builder prune --force || true | |
| # - name: Build and push multi-arch image | |
| # uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 | |
| # with: | |
| # builder: ${{ steps.buildx.outputs.name }} | |
| # context: . | |
| # file: ./${{ matrix.docker.name }} | |
| # push: true | |
| # cache-from: type=gha | |
| # cache-to: type=gha,mode=max | |
| # tags: ${{ steps.meta.outputs.tags }} | |
| # labels: ${{ steps.meta.outputs.labels }} | |
| # platforms: linux/amd64,linux/arm64/v8 | |
| # provenance: false | |
| # sbom: false | |
| # - name: Upload Docker build reports | |
| # if: always() | |
| # uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | |
| # with: | |
| # name: reports-docker-${{ matrix.docker.name }} | |
| # path: | | |
| # build/reports/ | |
| # build/test-results/ | |
| # build/reports/problems/ | |
| # retention-days: 3 | |
| # if-no-files-found: warn |