name: 'Tests'

on:
  push:
    branches:
      - main
      - develop
      - v4
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}
  cancel-in-progress: true

permissions:
  contents: read # to fetch code (actions/checkout)
  actions: read

# IMPORTANT
# Jobs using `nx affected` require nrwl/nx-set-shas, which in turn requires actions/checkout fetch-depth:0 (meaning unlimited)

jobs:
  conditions:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: read
    outputs:
      backend: ${{ steps.filter.outputs.backend }}
      frontend: ${{ steps.filter.outputs.frontend }}
      api: ${{ steps.filter.outputs.api }}
      e2e: ${{ steps.filter.outputs.e2e }}
      cli: ${{ steps.filter.outputs.cli }}
      docs: ${{ steps.filter.outputs.docs }}
      global: ${{ steps.filter.outputs.global }}
      is_local: ${{ github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 2 # need to check out previous 2 commits to see what has changed
      - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3
        id: filter
        with:
          filters: .github/filters.yaml

  # Always run build in case an unrecognized file change could affect it
  # Build must complete before any other step that uses build, so that it gets cached here and doesn't build multiple times
  build:
    name: 'build (node: ${{ matrix.node }})'
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build

  docs_build:
    name: 'docs build (node: ${{ matrix.node }})'
    runs-on: ubuntu-latest
    needs: [conditions, build]
    if: ${{ needs.conditions.outputs.docs == 'true' || needs.conditions.outputs.global == 'true' }}
    strategy:
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}

      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - name: Install docs dependencies
        working-directory: docs
        run: yarn install --frozen-lockfile
      - name: Run docs build
        working-directory: docs
        run: yarn build

  pretty:
    name: 'pretty (node: ${{ matrix.node }})'
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Run lint
        run: yarn prettier:check

  lint:
    name: 'lint (node: ${{ matrix.node }})'
    needs: [build] # build is needed so that imports can be verified
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0 # needed for nx-set-shas
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - name: Run lint
        run: yarn nx affected --target=lint --parallel --nx-ignore-cycles

  typescript:
    name: 'typescript (node: ${{ matrix.node }})'
    needs: [conditions, build]
    runs-on: ubuntu-latest
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.backend == 'true'
    strategy:
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0 # needed for nx-set-shas
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - name: TSC for packages
        run: yarn nx affected --target=test:ts --nx-ignore-cycles
      - name: TSC for back
        run: yarn nx affected --target=test:ts:back --nx-ignore-cycles
      - name: TSC for front
        run: yarn nx affected --target=test:ts:front --nx-ignore-cycles

  unit_back:
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true'
    name: 'unit_back (node: ${{ matrix.node }})'
    needs: [conditions, build]
    runs-on: ubuntu-latest
    env:
      YARN_ENABLE_IMMUTABLE_INSTALLS: false
    strategy:
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0 # needed for nx-set-shas
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - name: Run tests
        run: yarn nx affected --target=test:unit --nx-ignore-cycles -- --coverage

      - name: Upload coverage artifacts
        if: matrix.node == 24 # Only upload from one node version to avoid conflicts
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: unit-coverage-reports
          path: |
            coverage/
            packages/**/coverage/
          retention-days: 1

  unit_back_vitest:
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true'
    name: 'unit_back_vitest (node: ${{ matrix.node }})'
    needs: [conditions, build]
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - name: Run Vitest unit tests
        run: yarn test:unit:vitest

  unit_front:
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.frontend == 'true'
    name: 'unit_front (node: ${{ matrix.node }})'
    needs: [conditions, build]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false # remove once tests aren't flaky
      matrix:
        node: [24]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0 # needed for nx-set-shas
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - name: Run tests
        run: yarn nx affected --target=test:front --nx-ignore-cycles -- --runInBand --coverage

      - name: Upload coverage artifacts
        if: matrix.node == 24 # Only upload from one node version to avoid conflicts
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: front-coverage-reports
          path: |
            coverage/
            packages/**/coverage/
          retention-days: 1

  e2e_ce:
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.frontend == 'true' || needs.conditions.outputs.e2e == 'true'
    timeout-minutes: 60
    needs: [conditions, build]
    name: '[CE] e2e (browser: ${{ matrix.project }}) (shard: ${{ matrix.shard }})'
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false # remove once tests aren't flaky
      matrix:
        project: ['chromium', 'webkit', 'firefox']
        shard: [1/4, 2/4, 3/4, 4/4]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: 22
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Install Playwright Browsers
        run: npx playwright install --with-deps
      - name: Monorepo build
        uses: ./.github/actions/run-build

      - name: Run [CE] E2E tests
        uses: ./.github/actions/run-e2e-tests
        with:
          runEE: false
          jestOptions: '--project=${{ matrix.project }} --shard=${{ matrix.shard }}'

      - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        if: failure()
        with:
          name: ce-${{ matrix.project }}--playwright-trace-${{ github.run_id }}-${{ github.job }}
          path: test-apps/e2e/test-results/**/trace.zip
          retention-days: 1

        # Add the Trunk Analytics Uploader step
      - name: Upload results to trunk
        if: '!cancelled()'
        uses: trunk-io/analytics-uploader@1198efcc0976501147b10a7e343c3fa069527526 # v2.0.9
        with:
          junit-paths: test-apps/junit-reports/*.xml
          org-slug: strapi
          token: ${{ secrets.TRUNK_API_TOKEN }}
        continue-on-error: true

  e2e_ee:
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.frontend == 'true' || needs.conditions.outputs.e2e == 'true'
    timeout-minutes: 60
    needs: [conditions, build]
    name: '[EE] e2e (browser: ${{ matrix.project }}) (shard: ${{ matrix.shard }})'
    runs-on: ubuntu-latest
    env:
      STRAPI_LICENSE: ${{ secrets.strapiLicense }}
    strategy:
      fail-fast: false # remove once tests aren't flaky
      matrix:
        project: ['chromium', 'webkit', 'firefox']
        shard: [1/4, 2/4, 3/4, 4/4]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: 22

      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install

      - name: Install Playwright Browsers
        run: npx playwright install --with-deps

      - name: Monorepo build
        uses: ./.github/actions/run-build
      - name: Run [EE] E2E tests
        uses: ./.github/actions/run-e2e-tests
        with:
          runEE: true
          jestOptions: '--project=${{ matrix.project }} --shard=${{ matrix.shard }}'

      - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        if: failure()
        with:
          name: ee-${{ matrix.project }}--playwright-trace-${{ github.run_id }}-${{ github.job }}
          path: test-apps/e2e/test-results/**/trace.zip
          retention-days: 1

      # Add the Trunk Analytics Uploader step
      - name: Upload results to trunk
        if: '!cancelled()'
        uses: trunk-io/analytics-uploader@1198efcc0976501147b10a7e343c3fa069527526 # v2.0.9
        with:
          junit-paths: test-apps/junit-reports/*.xml
          org-slug: strapi
          token: ${{ secrets.TRUNK_API_TOKEN }}
        continue-on-error: true

  cli:
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.cli == 'true'
    timeout-minutes: 60
    needs: [conditions, build]
    name: 'CLI Tests (node: ${{ matrix.node }})'
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}

      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install

      - name: Monorepo build
        uses: ./.github/actions/run-build

      - name: Run CLI tests
        run: yarn test:cli

  api_ce_pg:
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.api == 'true'
    runs-on: ubuntu-latest
    needs: [conditions, build]
    name: '[CE] API Integration (postgres, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
    strategy:
      fail-fast: false
      matrix:
        node: [22, 24, 26]
        shard: [1/4, 2/4, 3/4, 4/4]
    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: strapi
          POSTGRES_PASSWORD: strapi
          POSTGRES_DB: strapi_test
        # Set health checks to wait until postgres has started
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
          -v /__w/.github/workflows/db/postgres:/docker-entrypoint-initdb.d
        ports:
          # Maps tcp port 5432 on service container to the host
          - 5432:5432
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - uses: ./.github/actions/run-api-tests
        with:
          dbOptions: '--dbclient=postgres --dbhost=localhost --dbport=5432 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
          jestOptions: '--shard=${{ matrix.shard }}'

  api_ce_mysql:
    if: needs.conditions.outputs.global == 'true' ||  needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.api == 'true'
    runs-on: ubuntu-latest
    needs: [conditions, build]
    name: '[CE] API Integration (mysql:latest, package: mysql2}, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
    strategy:
      fail-fast: false
      matrix:
        node: [22, 24, 26]
        shard: [1/4, 2/4, 3/4, 4/4]
    services:
      mysql:
        image: bitnamilegacy/mysql:latest
        env:
          MYSQL_ROOT_PASSWORD: strapi
          MYSQL_USER: strapi
          MYSQL_PASSWORD: strapi
          MYSQL_DATABASE: strapi_test
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3
        ports:
          # Maps tcp port 5432 on service container to the host
          - 3306:3306
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - uses: ./.github/actions/run-api-tests
        with:
          dbOptions: '--dbclient=mysql --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
          jestOptions: '--shard=${{ matrix.shard }}'

  api_ce_sqlite:
    if: needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.api == 'true'
    runs-on: ubuntu-latest
    needs: [conditions, build]
    name: '[CE] API Integration (sqlite, package: better-sqlite3, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
    strategy:
      fail-fast: false
      matrix:
        node: [22, 24, 26]
        shard: [1/4, 2/4, 3/4, 4/4]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - uses: ./.github/actions/run-api-tests
        with:
          dbOptions: '--dbclient=sqlite --dbfile=./tmp/data.db'
          jestOptions: '--shard=${{ matrix.shard }}'

  # EE
  api_ee_pg:
    runs-on: ubuntu-latest
    needs: [conditions, build]
    name: '[EE] API Integration (postgres, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
    if: (needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.api == 'true') && needs.conditions.outputs.is_local == 'true'
    env:
      STRAPI_LICENSE: ${{ secrets.strapiLicense }}
    strategy:
      fail-fast: false
      matrix:
        node: [22, 24, 26]
        shard: [1/5, 2/5, 3/5, 4/5, 5/5]
    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: strapi
          POSTGRES_PASSWORD: strapi
          POSTGRES_DB: strapi_test
        # Set health checks to wait until postgres has started
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
          -v /__w/.github/workflows/db/postgres:/docker-entrypoint-initdb.d
        ports:
          # Maps tcp port 5432 on service container to the host
          - 5432:5432
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - uses: ./.github/actions/run-api-tests
        with:
          dbOptions: '--dbclient=postgres --dbhost=localhost --dbport=5432 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
          runEE: true
          jestOptions: '--shard=${{ matrix.shard }}'

  api_ee_mysql:
    runs-on: ubuntu-latest
    needs: [conditions, build]
    name: '[EE] API Integration (mysql:latest, package: mysql2, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
    if: (needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.api == 'true') && needs.conditions.outputs.is_local == 'true'
    env:
      STRAPI_LICENSE: ${{ secrets.strapiLicense }}
    strategy:
      fail-fast: false
      matrix:
        node: [22, 24, 26]
        shard: [1/5, 2/5, 3/5, 4/5, 5/5]
    services:
      mysql:
        image: bitnamilegacy/mysql:latest
        env:
          MYSQL_ROOT_PASSWORD: strapi
          MYSQL_USER: strapi
          MYSQL_PASSWORD: strapi
          MYSQL_DATABASE: strapi_test
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3
        ports:
          # Maps tcp port 5432 on service container to the host
          - 3306:3306
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - uses: ./.github/actions/run-api-tests
        with:
          dbOptions: '--dbclient=mysql --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
          runEE: true
          jestOptions: '--shard=${{ matrix.shard }}'

  api_ee_sqlite:
    runs-on: ubuntu-latest
    needs: [conditions, build]
    name: '[EE] API Integration (sqlite, client: better-sqlite3, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
    if: (needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.api == 'true') && needs.conditions.outputs.is_local == 'true'
    env:
      STRAPI_LICENSE: ${{ secrets.strapiLicense }}
    strategy:
      fail-fast: false
      matrix:
        node: [22, 24, 26]
        shard: [1/4, 2/4, 3/4, 4/4]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}
      - name: Monorepo install
        uses: ./.github/actions/yarn-nm-install
      - name: Monorepo build
        uses: ./.github/actions/run-build
      - uses: ./.github/actions/run-api-tests
        with:
          dbOptions: '--dbclient=sqlite --dbfile=./tmp/data.db'
          runEE: true
          jestOptions: '--shard=${{ matrix.shard }}'
  sonarqube:
    name: SonarQube Analysis
    runs-on: ubuntu-latest
    # On pull requests, only run for local branches (is_local also excludes forks and
    # Dependabot, which cannot access repository secrets such as SONAR_TOKEN). Pushes to
    # develop/main still run the analysis.
    if: >-
      ${{ (github.event_name != 'pull_request' || needs.conditions.outputs.is_local == 'true')
      && (needs.conditions.outputs.global == 'true' || needs.conditions.outputs.backend == 'true' || needs.conditions.outputs.frontend == 'true') }}
    needs: [conditions, unit_back, unit_front]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis

      - name: Download unit test coverage
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: unit-coverage-reports
          path: unit-coverage
        continue-on-error: true

      - name: Download frontend test coverage
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: front-coverage-reports
          path: front-coverage
        continue-on-error: true

      - name: List coverage files (debug)
        run: find . -name "lcov.info" -type f

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@299e4b793aaa83bf2aba7c9c14bedbb485688ec4 # v7.1.0
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

      - name: SonarQube Quality Gate check
        id: sonarqube-quality-gate-check
        uses: SonarSource/sonarqube-quality-gate-action@cf038b0e0cdecfa9e56c198bbb7d21d751d62c3b # v1.2.0
        timeout-minutes: 5
        continue-on-error: true
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

  test_result:
    if: ${{ always() }}
    runs-on: ubuntu-latest
    name: aggregate_test_result
    needs:
      [
        pretty,
        lint,
        build,
        docs_build,
        typescript,
        unit_back,
        unit_back_vitest,
        unit_front,
        e2e_ce,
        e2e_ee,
        cli,
        api_ce_pg,
        api_ce_mysql,
        api_ce_sqlite,
        api_ee_pg,
        api_ee_mysql,
        api_ee_sqlite,
        sonarqube,
      ]
    steps:
      - run: exit 1
        if: >-
          ${{
                contains(needs.*.result, 'failure')
            || contains(needs.*.result, 'cancelled')
          }}
