name: 'Behat'

# Controls when the workflow will run
on:
  # Triggers the workflow on all pull request events
  pull_request: { }
  # Trigger workflow for release branches.
  push:
    branches:
      - main
      - "[0-9]+.[0-9]+.x"

# We use the default concurrency grouping of allowing a single workflow per branch/PR/tag to run at the same time.
# In case of PRs we only care about the results for the last workflow run, so we cancel workflows already in progress
# when new code is pushed, in all other cases (branches/tags) we want to have a history for commits so it's easier to
# find breakages when they occur (head_ref is non-empty only when the workflow is triggered from a PR).
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.head_ref != '' }}

# github.head_ref is only set when the workflow was triggered by a pull_request and it contains the value of the source branch of the PR.
# github.ref_name will than only be used if the workflow was not triggered by a pull_request and it also just contains the branch name.
env:
  BRANCH_NAME: ${{ github.head_ref || github.ref_name }}

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  feature_discovery:
    name: "Feature Discovery"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - run: sudo apt-get install jq

      - name: Find feature files
        id: set-matrix
        run: |
          echo -n "features=" >> $GITHUB_OUTPUT
          cd tests/behat/features/capabilities
          find * -type d -exec sh -c "ls {} | grep '\.feature$' > /dev/null" ';' -print | jq -R -s -c 'split("\n") | map(select(length > 0))' >> $GITHUB_OUTPUT

    outputs:
      features: ${{ steps.set-matrix.outputs.features }}

  install_previous_open_social:
    name: "Install previous Open Social major version"
    runs-on: ubuntu-22.04
    container:
      image: goalgorilla/open_social_docker:ci-drupal10-php8.1
      volumes:
        - ${{ github.workspace }}:/var/www

    strategy:
      matrix:
        with_optional:
          - ""
          - "with-optional"

    services:
      db:
        image: mariadb:10.7
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: social
      mailcatcher:
        image: schickling/mailcatcher
        env:
          VIRTUAL_HOST: mailcatcher.social.dev
          VIRTUAL_PORT: 1080
        ports:
          - "1080"
      redis:
        image: redis:latest
      solr:
        image: ghcr.io/goalgorilla/ci-solr:8.11
        env:
          SOLR_SOLR_MEM_SIZE: 512m
          PARTIAL_SEARCH_ENABLED: 0
        options: --health-cmd="solr status" --health-interval=10s --health-timeout=5s --health-retries=3

    defaults:
      run:
        shell: bash
        working-directory: /var/www

    steps:
      - uses: actions/checkout@v4
        with:
          path: ${{ github.workspace }}/tmp

      # Checkout Drupal Social.
      - name: Prepare composer
        run: |
          cp tmp/tests/composer.json composer.json

      - name: Set-up the previous major version of Open Social
        run: |
          set -e

          apt-get install -y jq
          # This gets all version info from composer for Open Social. We then
          # use jq to traverse and manipulate the JSON data to find ourselves
          # the current stable major version.
          PREVIOUS_MAJOR=`composer info -a --format json goalgorilla/open_social | jq '.versions | map(select(contains("-") == false)) | map(split(".")[0] | tonumber) | unique | reverse | .[0]'`
          echo "Setting up update test from Open Social $PREVIOUS_MAJOR"

          # Also include a hardcoded version for instaclick/php-webdriver, drupal/dynamic_entity_reference and drupal/core
          # as we didnt lock the version, and the patch isn't applying on the latest, so we require it
          # specifically to match the version that was installed at that moment in time.
          composer require goalgorilla/open_social:~$PREVIOUS_MAJOR instaclick/php-webdriver:1.4.16 drupal/dynamic_entity_reference:3.1.0 drupal/core:10.2.3 "drupal/socialbase:2.5.19"

          # Installation
          # This is purposefully duplicated because we may change how
          # installation works between major versions, so this provides us the
          # flexibility to reflect that in the workflow.
          cp tmp/tests/default.settings.php /var/www/html/sites/default/default.settings.php
          mkdir /var/www/files_private

          export OPTIONAL=""
          if [[ "${{ matrix.with_optional }}" == "with-optional" ]]; then
            export OPTIONAL="social_module_configure_form.select_all='TRUE'"
          fi

          drush site-install -y social --db-url=mysql://root:root@db:3306/social $OPTIONAL install_configure_form.update_status_module='array(FALSE,FALSE)' --site-name='Open Social';

          # Make sure swiftmailer is configured for our CI.
          drush cset -y swiftmailer.transport transport 'smtp'
          drush cset -y swiftmailer.transport smtp_host 'mailcatcher'
          drush cset -y swiftmailer.transport smtp_port 1025

          # Dump the database to our test-output folder so that we can locally
          # debug if the update fails.
          mkdir -p behat-test-output
          if [[ "${{ matrix.with_optional }}" == "with-optional" ]]; then
            drush sql-dump > behat-test-output/pre-update-with-optional.sql
          else
            drush sql-dump > behat-test-output/pre-update.sql
          fi

          # Remove the composer files since we'll recreate this in the update.
          rm composer.*

      - name: Clean up checkout
        run: rm -r tmp

      - name: Package up previous version
        uses: actions/cache@v3
        with:
          path: ${{ github.workspace }}
          key: ${{ github.sha }}-previous-version-${{ matrix.with_optional }}

  install_open_social:
    needs: [install_previous_open_social]

    name: "Install Open Social"
    runs-on: ubuntu-22.04
    container:
      image: goalgorilla/open_social_docker:ci-drupal10-php8.1
      volumes:
        - ${{ github.workspace }}:/var/www

    strategy:
      matrix:
        update:
          - ""
          - "update"
        with_optional:
          - ""
          - "with-optional"

    services:
      db:
        image: mariadb:10.7
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: social
      mailcatcher:
        image: schickling/mailcatcher
        env:
          VIRTUAL_HOST: mailcatcher.social.dev
          VIRTUAL_PORT: 1080
        ports:
          - "1080"
      redis:
        image: redis:latest
      solr:
        image: ghcr.io/goalgorilla/ci-solr:8.11
        env:
          SOLR_SOLR_MEM_SIZE: 512m
          PARTIAL_SEARCH_ENABLED: 0

    defaults:
      run:
        shell: bash
        working-directory: /var/www

    steps:
      - name: Retrieve previous version
        if: ${{ matrix.update == 'update' }}
        uses: actions/cache@v3
        with:
          fail-on-cache-miss: true
          path: ${{ github.workspace }}
          key: ${{ github.sha }}-previous-version-${{ matrix.with_optional }}

      - uses: actions/checkout@v4
        with:
          path: ${{ github.workspace }}/tmp

      - name: Composer install to HEAD
        run: |
          set -e

          cp tmp/tests/composer.json composer.json

          # Composer has special handling for "version-like" branch names
          if [[ $BRANCH_NAME =~ [0-9]+\.[0-9]+\.x ]]; then
            composer require goalgorilla/open_social:$BRANCH_NAME-dev
          else
            composer require goalgorilla/open_social:dev-$BRANCH_NAME
          fi

          composer install

          cp tmp/tests/default.settings.php /var/www/html/sites/default/default.settings.php
          mkdir -p /var/www/files_private

          rm -r tmp/

      - name: Update the previous major version to HEAD
        if: ${{ matrix.update == 'update' }}
        run: |
          set -e

          if [[ "${{ matrix.with_optional }}" == "with-optional" ]]; then
            drush sqlc < behat-test-output/pre-update-with-optional.sql
          else
            drush sqlc < behat-test-output/pre-update.sql
          fi

          vendor/bin/drush updb -y 2> >(tee update.log >&2)

          # Make sure symfony_mailer is configured for our CI.
          # Set config after updates when the config exist.
          drush cset symfony_mailer.mailer_transport.sendmail plugin 'smtp' -y
          drush cset symfony_mailer.mailer_transport.sendmail configuration.user '' -y
          drush cset symfony_mailer.mailer_transport.sendmail configuration.pass '' -y
          drush cset symfony_mailer.mailer_transport.sendmail configuration.host 'mailcatcher' -y
          drush cset symfony_mailer.mailer_transport.sendmail configuration.port '1025' -y

          # Ensure there are no warnings or requirements errors in the update.log
          if grep -E '^>\s+\[warning\]\s' update.log 1>/dev/null || grep 'Requirements check reports errors. Do you wish to continue?' update.log 1>/dev/null; then
            # Move the update.log with warnings/errors and status messages to the test output. The text is viewable on
            # GitHub too but some people might find it easier to just download the artifact and get digging.
            if [[ "${{ matrix.with_optional }}" == "with-optional" ]]; then
              mv update.log behat-test-output/update-with-optional.log
            else
              mv update.log behat-test-output/update.log
            fi

            # Abort our testing until the warning/error is fixed and let our user know why we stopped.
            echo "The drush output should not contain any warnings or requirements errors"
            exit 1
          fi

      - name: Set-up a new Open Social installation at HEAD
        if: ${{ matrix.update == '' }}
        run: |
          set -e

          export OPTIONAL=""
          if [[ "${{ matrix.with_optional }}" == "with-optional" ]]; then
            export OPTIONAL="social_module_configure_form.select_all='TRUE'"
          fi

          drush site-install -y social --db-url=mysql://root:root@db:3306/social $OPTIONAL install_configure_form.update_status_module='array(FALSE,FALSE)' --site-name='Open Social';

          # Make sure symfony_mailer is configured for our CI.
          drush cset symfony_mailer.mailer_transport.sendmail plugin 'smtp' -y
          drush cset symfony_mailer.mailer_transport.sendmail configuration.user '' -y
          drush cset symfony_mailer.mailer_transport.sendmail configuration.pass '' -y
          drush cset symfony_mailer.mailer_transport.sendmail configuration.host 'mailcatcher' -y
          drush cset symfony_mailer.mailer_transport.sendmail configuration.port '1025' -y

      - name: Dump Database
        run: |
          mkdir -p behat-test-output
          if [[ ! -z "${{ matrix.update }}" ]] && [[ ! -z "${{ matrix.with_optional }}" ]]; then
            drush sql:dump > behat-test-output/installation-update-with-optional.sql
          elif [[ ! -z "${{ matrix.update }}" ]]; then
            drush sql:dump > behat-test-output/installation-update.sql
          elif [[ ! -z "${{ matrix.with_optional }}" ]]; then
            drush sql:dump > behat-test-output/installation-with-optional.sql
          else
            drush sql:dump > behat-test-output/installation.sql
          fi

      - name: Fix owner of web files
        run: chown -R www-data:www-data /var/www

      - name: Package up site
        uses: actions/cache@v3
        with:
          path: ${{ github.workspace }}
          key: ${{ github.sha }}-test-setup-${{ matrix.update}}${{ matrix.with_optional }}

      - name: Upload Installation database
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: behat-test-output
          path: behat-test-output

  # This workflow contains a single job called "Running tests"
  tests:
    needs: [feature_discovery, install_open_social]

    name: 'Tests'
    runs-on: ubuntu-22.04
    container:
      image: goalgorilla/open_social_docker:ci-drupal10-php8.1
      volumes:
        - ${{ github.workspace }}:/var/www

    strategy:
      fail-fast: false
      matrix:
        feature: ${{ fromJson(needs.feature_discovery.outputs.features) }}
        update:
          - ""
          - "update"
        with_optional:
          - ""
          - "with-optional"

    services:
      web:
        image: goalgorilla/open_social_docker:ci-drupal10-php8.1
        env:
          DRUPAL_SETTINGS: production
        volumes:
          - ${{ github.workspace }}:/var/www
        ports:
          - "80"
      db:
        image: mariadb:10.7
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: social
      mailcatcher:
        image: schickling/mailcatcher
        env:
          VIRTUAL_HOST: mailcatcher.social.dev
          VIRTUAL_PORT: 1080
        ports:
          - "1080"
      redis:
        image: redis:latest
      solr:
        image: ghcr.io/goalgorilla/ci-solr:8.11
        env:
          SOLR_SOLR_MEM_SIZE: 512m
          PARTIAL_SEARCH_ENABLED: 0
      chrome:
        # We use an image based on zenika/alpine-chrome:latest so we can add
        # the start-up arguments that we need.
        image: kingdutch/social-docker-chrome
        ports:
          - '9222:9222'

    defaults:
      run:
        shell: bash
        working-directory: /var/www

    steps:
      - name: Download Site
        uses: actions/cache@v3
        with:
          fail-on-cache-miss: true
          path: ${{ github.workspace }}
          key: ${{ github.sha }}-test-setup-${{ matrix.update}}${{ matrix.with_optional }}

      # We could run all tests at once by passing the folder directly to behat
      # however, by doing this manually in a loop we have the ability to ensure
      # each test runs against a clean database cheaply.
      - name: Run Integration test
        run: |
          if [[ ! -z "${{ matrix.update }}" ]] && [[ ! -z "${{ matrix.with_optional }}" ]]; then
            export TEST_DATABASE=`pwd`/behat-test-output/installation-update-with-optional.sql
          elif [[ ! -z "${{ matrix.update }}" ]]; then
            export TEST_DATABASE=`pwd`/behat-test-output/installation-update.sql
          elif [[ ! -z "${{ matrix.with_optional }}" ]]; then
            export TEST_DATABASE=`pwd`/behat-test-output/installation-with-optional.sql
          else
            export TEST_DATABASE=`pwd`/behat-test-output/installation.sql
          fi
          vendor/bin/behat --version
          for test in html/profiles/contrib/social/tests/behat/features/capabilities/${{ matrix.feature }}/*.feature; do
            if head -n1 $test | grep -q "@disabled"; then
              echo "$test - Skipping disabled test"
              continue
            fi

            # If we're testing a clean install (i.e. not updating) then ignore
            # any tests that have opted out of install testing.
            if [[ -z "${{ matrix.update }}" ]]; then
              export TAGS="--tags=~no-install"
            # Do the same when we are testing with an update.
            else
              export TAGS="--tags=~no-update"
            fi

            # Tests with the @no-database tag import their own scaffold so they
            # ignore the databases provided by our test matrix, that's why we
            # only run them once when all our matrix values are off.
            if [[ ! -z "${{ matrix.update }}" ]] || [[ ! -z "${{ matrix.with_optional }}" ]]; then
              export TAGS="$TAGS&&~no-database"
            fi

            echo "::group::$test"
            # Run in subshell to make the test command visible and copyable.
            (set -x; vendor/bin/behat -vv --config html/profiles/contrib/social/tests/behat/behat.yml --colors --allow-empty $TAGS $test)
            echo "::endgroup::"
          done

      - name: Move test artifacts into upload folder
        if: failure()
        run: |
          set -e

          # Make the output folder
          if [[ ! -z "${{ matrix.update }}" ]] && [[ ! -z "${{ matrix.with_optional }}" ]]; then
            export OUTPUT_FOLDER=behat-test-output/${{ matrix.feature }}/update-with-optional
          elif [[ ! -z "${{ matrix.update }}" ]]; then
            export OUTPUT_FOLDER=behat-test-output/${{ matrix.feature }}/update
          elif [[ ! -z "${{ matrix.with_optional }}" ]]; then
            export OUTPUT_FOLDER=behat-test-output/${{ matrix.feature }}/with-optional
          else
            export OUTPUT_FOLDER=behat-test-output/${{ matrix.feature }}
          fi
          mkdir -p $OUTPUT_FOLDER

          # Move test results to the output folder if they exist
          if (shopt -s nullglob; f=(html/profiles/contrib/social/tests/behat/logs/*); ((${#f[@]}))); then
            mv html/profiles/contrib/social/tests/behat/logs/* $OUTPUT_FOLDER/
          fi

          # Move files from the mail spool to the output folder if they exist
          if (shopt -s nullglob; f=(html/profiles/contrib/social/tests/behat/mail-spool/*); ((${#f[@]}))); then
            mv html/profiles/contrib/social/tests/behat/mail-spool/* $OUTPUT_FOLDER/
          fi

          # Dump the database with the state of the test failure to allow for
          # local inspection.
          drush sql-dump > $OUTPUT_FOLDER/at-test-failure.sql

      - name: Upload Behat Test Output
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: behat-test-output
          path: behat-test-output
