name: Elixir
on:
  workflow_call:

jobs:
  unit-test:
    runs-on: ubuntu-22.04
    defaults:
      run:
        working-directory: ./elixir
    permissions:
      checks: write
    env:
      MIX_ENV: test
      POSTGRES_HOST: localhost
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    services:
      postgres:
        image: postgres:15.2
        ports:
          - 5432:5432
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready --health-interval 10s --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - name: Tool Versions
        id: versions
        uses: marocchino/tool-versions-action@v1.1.1
      - uses: erlef/setup-beam@v1
        id: setup-beam
        with:
          otp-version: ${{ steps.versions.outputs.erlang }}
          elixir-version: ${{ steps.versions.outputs.elixir }}
      - uses: actions/cache/restore@v3
        name: Restore Elixir Deps Cache
        id: cache
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Install Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.get --only $MIX_ENV
      - name: Compile Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.compile --skip-umbrella-children
      - uses: actions/cache/save@v3
        name: Save Elixir Deps Cache
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Compile Application
        run: mix compile --warnings-as-errors
      - name: Setup Database
        run: |
          mix ecto.create
          mix ecto.migrate
      - name: Run Tests
        env:
          E2E_MAX_WAIT_SECONDS: 20
        run: |
          mix test --warnings-as-errors
      - name: Test Report
        uses: dorny/test-reporter@v1
        if:
          ${{ github.event.pull_request.head.repo.full_name == github.repository
          && (success() || failure()) }}
        with:
          name: Elixir Unit Test Report
          path: elixir/_build/test/lib/*/test-junit-report.xml
          reporter: java-junit

  type-check:
    runs-on: ubuntu-22.04
    defaults:
      run:
        working-directory: ./elixir
    env:
      # We need to set MIX_ENV to dev to make sure that we won't type-check our test helpers
      MIX_ENV: dev
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - name: Tool Versions
        id: versions
        uses: marocchino/tool-versions-action@v1.1.1
      - uses: erlef/setup-beam@v1
        id: setup-beam
        with:
          otp-version: ${{ steps.versions.outputs.erlang }}
          elixir-version: ${{ steps.versions.outputs.elixir }}
      - uses: actions/cache/restore@v3
        name: Restore Elixir Deps Cache
        id: cache
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Install Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.get --only $MIX_ENV
      - name: Compile Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.compile --skip-umbrella-children
      - uses: actions/cache/save@v3
        name: Save Elixir Deps Cache
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Compile Application
        run: mix compile --warnings-as-errors
      - uses: actions/cache/restore@v3
        name: Restore PLT cache
        id: plt_cache
        with:
          path: elixir/priv/plts
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ hashFiles('elixir/mix.lock') }}
          # This will make sure that we can incrementally build the PLT from older cache and save it under a new key
          restore-keys: |
            ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-
      - name: Create PLTs
        if: ${{ steps.plt_cache.outputs.cache-hit != 'true' }}
        run: mix dialyzer --plt
      - uses: actions/cache/save@v3
        if: ${{ steps.plt_cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' }}
        name: Save PLT cache
        with:
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ hashFiles('elixir/mix.lock') }}
          path: elixir/priv/plts
      - name: Run Dialyzer
        run: mix dialyzer --format dialyxir

  static-analysis:
    runs-on: ubuntu-22.04
    defaults:
      run:
        working-directory: ./elixir
    env:
      MIX_ENV: test
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - name: Tool Versions
        id: versions
        uses: marocchino/tool-versions-action@v1.1.1
      - uses: erlef/setup-beam@v1
        id: setup-beam
        with:
          otp-version: ${{ steps.versions.outputs.erlang }}
          elixir-version: ${{ steps.versions.outputs.elixir }}
      - uses: actions/cache/restore@v3
        name: Restore Elixir Deps Cache
        id: cache
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Install Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.get --only $MIX_ENV
      - name: Compile Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.compile --skip-umbrella-children
      - uses: actions/cache/save@v3
        name: Save Elixir Deps Cache
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Compile Application
        run: mix compile --force --warnings-as-errors
      - name: Check Formatting
        run: mix format --check-formatted
      - name: Check For Retired Packages
        run: mix hex.audit
      - name: Check For Vulnerable Packages
        run: mix deps.audit
      - name: Run Sobelow vulnerability scanner for web app
        working-directory: ./elixir/apps/web
        run: mix sobelow --skip
      - name: Run Credo
        run: mix credo --strict
      - name: Check for unused deps
        run: mix deps.unlock --check-unused

  migrations-and-seed-test:
    runs-on: ubuntu-22.04
    defaults:
      run:
        working-directory: ./elixir
    env:
      MIX_ENV: dev
      POSTGRES_HOST: localhost
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      MAIN_BRANCH: main
    services:
      postgres:
        image: postgres:15.2
        ports:
          - 5432:5432
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready --health-interval 10s --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - name: Install package dependencies
        run: |
          sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
          wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null
          sudo apt update
          sudo apt-get install -q -y postgresql-client
      - name: Tool Versions
        id: versions
        uses: marocchino/tool-versions-action@v1.1.1
      - uses: erlef/setup-beam@v1
        id: setup-beam
        with:
          otp-version: ${{ steps.versions.outputs.erlang }}
          elixir-version: ${{ steps.versions.outputs.elixir }}
      - uses: actions/cache/restore@v3
        name: Restore Elixir Deps Cache
        id: cache
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Install Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.get --only $MIX_ENV
      - name: Compile Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.compile --skip-umbrella-children
      - uses: actions/cache/save@v3
        name: Save Elixir Deps Cache
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Compile
        run: mix compile --warnings-as-errors
      - name: Download main branch DB dump
        id: download-artifact
        if: ${{ !contains(github.ref, env.MAIN_BRANCH) }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -xe

          REPO="${{ github.repository }}"
          WORKFLOW="cd.yml"
          ARTIFACT_NAME="db-dump"
          DESTINATION="apps/domain/priv/repo"

          ARTIFACTS_URL=$(
            gh api \
              -H "Accept: application/vnd.github+json" \
              -H "X-GitHub-Api-Version: 2022-11-28" \
              "/repos/${REPO}/actions/workflows/${WORKFLOW}/runs?event=push&branch=${{ env.MAIN_BRANCH }}&status=success&per_page=1" \
              --jq ".workflow_runs[0].artifacts_url"
          )

          DOWNLOAD_URL=$(
            gh api \
              -H "Accept: application/vnd.github+json" \
              -H "X-GitHub-Api-Version: 2022-11-28" \
              "${ARTIFACTS_URL}" \
              --jq '.artifacts[] | select(.name == "'${ARTIFACT_NAME}'") | .archive_download_url'
          )

          set +x
          curl -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -L -o "${DESTINATION}.zip" "$DOWNLOAD_URL"

          set -x
          unzip "${DESTINATION}.zip" -d "${DESTINATION}"
          rm "${DESTINATION}.zip"
      - name: Create Database
        run: |
          mix ecto.create
      - name: Restore DB dump
        if: ${{ !contains(github.ref, env.MAIN_BRANCH) }}
        env:
          PGPASSWORD: postgres
        run: |
          mix ecto.load
      - name: Run new migrations
        run: |
          mix ecto.migrate
      - name: Dump DB
        if: contains(github.ref, env.MAIN_BRANCH)
        env:
          PGPASSWORD: postgres
        run: |
          pg_dump firezone_dev \
            -U postgres -h localhost \
            --file apps/domain/priv/repo/structure.sql \
            --no-acl \
            --no-owner
      - name: Upload main branch DB dump
        if: contains(github.ref, env.MAIN_BRANCH)
        uses: actions/upload-artifact@v4
        with:
          name: db-dump
          path: elixir/apps/domain/priv/repo/structure.sql
      - name: Run Seed
        run: mix ecto.seed

  acceptance-test:
    permissions:
      checks: write
    runs-on: ubuntu-22.04
    defaults:
      run:
        working-directory: ./elixir
    env:
      MIX_ENV: test
      POSTGRES_HOST: localhost
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      MIX_TEST_PARTITIONS: 1
    strategy:
      fail-fast: false
      matrix:
        MIX_TEST_PARTITION: [1]
    services:
      postgres:
        image: postgres:15.2
        ports:
          - 5432:5432
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready --health-interval 10s --health-timeout 5s
          --health-retries 5
      vault:
        image: vault:1.12.2
        env:
          VAULT_ADDR: "http://127.0.0.1:8200"
          VAULT_DEV_ROOT_TOKEN_ID: "firezone"
        ports:
          - 8200:8200/tcp
        options: --cap-add=IPC_LOCK
    steps:
      - uses: actions/checkout@v4
      - uses: nanasess/setup-chromedriver@v2
      - run: |
          export DISPLAY=:99
          chromedriver --url-base=/wd/hub &
          sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
      - name: Tool Versions
        id: versions
        uses: marocchino/tool-versions-action@v1.1.1
      - uses: erlef/setup-beam@v1
        id: setup-beam
        with:
          otp-version: ${{ steps.versions.outputs.erlang }}
          elixir-version: ${{ steps.versions.outputs.elixir }}
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ steps.versions.outputs.nodejs }}
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      # Elixir cache
      - uses: actions/cache/restore@v3
        name: Restore Elixir Deps Cache
        id: cache
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Install Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.get --only $MIX_ENV
      - name: Compile Dependencies
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        run: mix deps.compile --skip-umbrella-children
      - uses: actions/cache/save@v3
        name: Save Elixir Deps Cache
        if: ${{ steps.cache.outputs.cache-hit != 'true' }}
        env:
          cache-name: elixir-deps-${{ env.MIX_ENV }}
        with:
          path: |
            elixir/deps
            elixir/_build/${{ env.MIX_ENV }}
          key: ubuntu-22.04-${{ runner.arch }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ env.cache-name }}-${{ hashFiles('elixir/mix.lock') }}
      - name: Compile Application
        run: mix compile --warnings-as-errors
      # Front-End deps cache
      - uses: actions/cache/restore@v3
        name: pnpm Web Deps Cache
        id: pnpm-web-deps-cache
        env:
          cache-name: pnpm-deps-web
        with:
          path: |
            elixir/apps/web/assets/node_modules
            elixir/esbuild-*
            elixir/tailwind-*
          key: ubuntu-22.04-${{ runner.arch }}-${{ env.cache-name }}-${{ hashFiles('elixir/apps/web/assets/pnpm-lock.yaml') }}
      - name: Install Front-End Dependencies
        if: ${{ steps.pnpm-web-deps-cache.outputs.cache-hit != 'true' }}
        run: |
          cd apps/web
          mix assets.setup
      - uses: actions/cache/save@v3
        name: Save pnpm Deps Cache
        if: ${{ steps.pnpm-web-deps-cache.outputs.cache-hit != 'true' }}
        env:
          cache-name: pnpm-deps-web
        with:
          path: |
            elixir/apps/web/assets/node_modules
            elixir/esbuild-*
            elixir/tailwind-*
          key: ubuntu-22.04-${{ runner.arch }}-${{ env.cache-name }}-${{ hashFiles('elixir/apps/web/assets/pnpm-lock.yaml') }}
      # Front-End build cache, it rarely changes so we cache it agressively too
      - uses: actions/cache/restore@v3
        name: Web Assets Cache
        id: pnpm-web-build-cache
        env:
          cache-name: pnpm-build-web
        with:
          path: |
            elixir/apps/web/assets/tmp
            elixir/apps/web/priv/static
          key: ubuntu-22.04-${{ runner.arch }}-${{ env.cache-name }}-${{ hashFiles('elixir/apps/web/assets/**') }}
      - name: Build Web Assets
        if: ${{ steps.pnpm-web-build-cache.outputs.cache-hit != 'true' }}
        run: |
          cd apps/web
          mix assets.build
      - uses: actions/cache/save@v3
        name: Save Web Assets Cache
        if: ${{ steps.pnpm-web-build-cache.outputs.cache-hit != 'true' }}
        env:
          cache-name: pnpm-build-web
        with:
          path: |
            elixir/apps/web/assets/tmp
            elixir/apps/web/priv/static
          key: ubuntu-22.04-${{ runner.arch }}-${{ env.cache-name }}-${{ hashFiles('elixir/apps/web/assets/**') }}
      # Run tests
      - name: Setup Database
        run: |
          mix ecto.create
          mix ecto.migrate
      - name: Run Acceptance Tests
        env:
          MIX_TEST_PARTITION: ${{ matrix.MIX_TEST_PARTITION }}
          E2E_MAX_WAIT_SECONDS: 5
        run: |
          mix test --only acceptance:true \
                   --partitions=${{ env.MIX_TEST_PARTITIONS }} \
                   --no-compile \
                   --no-archives-check \
                   --no-deps-check \
              || pkill -f chromedriver \
              || mix test --only acceptance:true --failed \
              || pkill -f chromedriver \
              || mix test --only acceptance:true --failed
      - name: Save Screenshots
        if:
          ${{ github.event.pull_request.head.repo.full_name == github.repository
          && always() }}
        uses: actions/upload-artifact@v4
        with:
          name: screenshots-${{ matrix.MIX_TEST_PARTITION }}
          path: elixir/apps/web/screenshots
      - name: Test Report
        uses: dorny/test-reporter@v1
        if:
          ${{ github.event.pull_request.head.repo.full_name == github.repository
          && (success() || failure()) }}
        with:
          name: Elixir Acceptance Test Report
          path: elixir/_build/test/lib/*/test-junit-report.xml
          reporter: java-junit
