release: v2.2.7 #291
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: Test | |
| on: | |
| pull_request: | |
| branches: [main, develop] | |
| workflow_call: | |
| inputs: | |
| test_path: | |
| description: Optional path passed to pytest (file, directory, or expression). | |
| required: false | |
| type: string | |
| default: "" | |
| env: | |
| CONTAINER_ROOT_DIR: /home/app/ | |
| DB_CONNECTION_TEST_MAX_ATTEMPTS: 10 | |
| DB_CONNECTION_TEST_SLEEP_INTERVAL: 5 | |
| ENV: ci_test | |
| APP_VERSION: 'test' | |
| FILE_UPLOAD_ENABLED: true | |
| TMP_UPLOADED_FILES: /tmp/ci-uploaded/ | |
| METADATA_SESSION_DIR: /tmp/ci-metadata-sessions/ | |
| MEDIA_DIR: /tmp/ci-media/ | |
| LIBRARIES_DIR: /tmp/ci-media/libraries/ | |
| DB_IS_NEEDED: true | |
| DB_DATA_DIR: /var/lib/postgresql/data | |
| DB_SUPERUSER_NAME: postgres | |
| DB_PORT: '5432' | |
| AFP_PORT: '3002' | |
| AFP_POOL_DIR_EXTERNAL: /tmp/pool/ | |
| AFP_POST_ENDPOINT: fingerprint-audio | |
| TMTA_USERNAME: tmta | |
| SUPERADMIN_USERNAME: ci_superadmin | |
| SUPERADMIN_PASSWORD: ci_superadmin_test | |
| SUPERADMIN_EMAIL: ci_superadmin@example.com | |
| DEMO_USERNAME: ci_demo | |
| DEMO_PASSWORD: ci_demo_test | |
| DEMO_EMAIL: ci_demo@example.com | |
| jobs: | |
| pre-commit: | |
| name: Pre-commit | |
| runs-on: ubuntu-latest | |
| # Dummy identity + flags only. Inherits workflow paths for FILE_UPLOAD (models need LIBRARIES_DIR at import). | |
| # No GitHub Environment or secrets: DB and external integrations off for mypy-only Django load. | |
| env: | |
| APP_NAME: htmt_api_mypy_ci | |
| APP_TITLE: HTMT API (mypy CI) | |
| DEBUG: "true" | |
| APP_IS_EXPOSED: "false" | |
| DB_IS_NEEDED: "false" | |
| AFP_ENABLED: "false" | |
| MUSICBRAINZ_LOOKUP_ENABLED: "false" | |
| SPOTIFY_ENABLED: "false" | |
| GOOGLE_OAUTH_ENABLED: "false" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| - name: Cache pip | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-pre-commit-${{ hashFiles('**/pyproject.toml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip- | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -e ".[dev]" | |
| - name: Set up filesystem for Django (mypy) | |
| run: bash scripts/setup-filesystem.sh | |
| - name: Install actionlint | |
| run: bash scripts/install-actionlint.sh | |
| - name: Run pre-commit | |
| run: pre-commit run --all-files --verbose --show-diff-on-failure | |
| check-vars-and-secrets: | |
| name: Check vars | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: ci_test | |
| steps: | |
| - uses: actions/checkout@v5.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check required vars (no app DB secrets for pytest) | |
| env: | |
| API_DIR_NAME: ${{ vars.API_DIR_NAME }} | |
| APP_TITLE: ${{ vars.APP_TITLE }} | |
| APP_NAME: ${{ vars.HTMT_API_APP_NAME }} | |
| DB_APP_NAME_SUFFIX: ${{ vars.DB_APP_NAME_SUFFIX }} | |
| GHCR_IMAGE_NAMESPACE: ${{ vars.GHCR_IMAGE_NAMESPACE }} | |
| DB_CONTAINER_NAME: ${{ format('{0}{1}', vars.HTMT_API_APP_NAME, vars.DB_APP_NAME_SUFFIX) }} | |
| AFP_CONTAINER_NAME: ${{ vars.AFP_APP_NAME }} | |
| AFP_IMAGE_REPO: ${{ vars.AFP_IMAGE_REPO }} | |
| AFP_VERSION: ${{ vars.AFP_VERSION }} | |
| run: bash scripts/check-workflow-env.sh API_DIR_NAME APP_TITLE APP_NAME DB_APP_NAME_SUFFIX GHCR_IMAGE_NAMESPACE DB_CONTAINER_NAME AFP_CONTAINER_NAME AFP_IMAGE_REPO AFP_VERSION | |
| pytest: | |
| name: Pytest | |
| needs: [check-vars-and-secrets] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: read | |
| checks: write | |
| pull-requests: write | |
| environment: | |
| name: ci_test | |
| env: | |
| ENV: ci_test | |
| API_DIR_NAME: ${{ vars.API_DIR_NAME }} | |
| APP_TITLE: ${{ vars.APP_TITLE }} | |
| APP_NAME: ${{ vars.HTMT_API_APP_NAME }} | |
| DB_CONTAINER_NAME: db | |
| AFP_CONTAINER_NAME: afp | |
| AFP_IMAGE_REPO: ${{ vars.AFP_IMAGE_REPO }} | |
| AFP_VERSION: ${{ vars.AFP_VERSION }} | |
| DEBUG: true | |
| DB_IS_NEEDED: true | |
| APP_IS_EXPOSED: false | |
| AFP_ENABLED: true | |
| SPOTIFY_ENABLED: true | |
| SPOTIFY_CLIENT_ID: test | |
| SPOTIFY_CLIENT_SECRET: test | |
| SPOTIFY_REDIRECT_URI: http://test/callback | |
| SPOTIFY_SCOPES: test | |
| GOOGLE_OAUTH_ENABLED: true | |
| GOOGLE_CLIENT_ID: test | |
| GOOGLE_CLIENT_SECRET: test | |
| GOOGLE_REDIRECT_URI: http://test/callback | |
| MUSICBRAINZ_LOOKUP_ENABLED: true | |
| ACOUSTID_API_KEY: ci-fake-acoustid-key | |
| # Ephemeral CI DB: same defaults as docker-compose.yml (not staging/prod secrets). | |
| DB_SUPERUSER_PASSWORD: postgres | |
| DB_APP_DB_NAME: htmt_api | |
| DB_APP_USERNAME: htmt_api_user | |
| DB_APP_USER_PASSWORD: htmt_api_password | |
| DJANGO_SECRET_KEY: dev-only-secret-key | |
| DB_URL: db | |
| AFP_URL: afp | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5.0.1 | |
| - name: GHCR image namespace for Compose (lowercase) | |
| shell: bash | |
| env: | |
| GHCR_NS: ${{ vars.GHCR_IMAGE_NAMESPACE }} | |
| run: | | |
| set -euo pipefail | |
| ns_lc="${GHCR_NS,,}" | |
| if [ -z "$ns_lc" ]; then | |
| echo "ERROR: GHCR_IMAGE_NAMESPACE GitHub Variable is empty." | |
| exit 1 | |
| fi | |
| echo "GHCR_IMAGE_NAMESPACE=${ns_lc}" >> "${GITHUB_ENV}" | |
| - name: Log in to ghcr.io for afp pull | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| docker logout ghcr.io 2>/dev/null || true | |
| owner_lc="${GITHUB_REPOSITORY_OWNER,,}" | |
| if [ -n "${GHCR_READ_PACKAGES_TOKEN:-}" ]; then | |
| if [ -z "${GHCR_READ_PACKAGES_USERNAME:-}" ]; then | |
| echo "ERROR: GHCR_READ_PACKAGES_TOKEN is set but GHCR_READ_PACKAGES_USERNAME is empty." | |
| exit 1 | |
| fi | |
| echo "${GHCR_READ_PACKAGES_TOKEN}" | docker login ghcr.io -u "${GHCR_READ_PACKAGES_USERNAME}" --password-stdin | |
| else | |
| echo "${GITHUB_TOKEN}" | docker login ghcr.io -u "${owner_lc}" --password-stdin | |
| fi | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GHCR_READ_PACKAGES_TOKEN: ${{ secrets.GHCR_READ_PACKAGES_TOKEN }} | |
| GHCR_READ_PACKAGES_USERNAME: ${{ secrets.GHCR_READ_PACKAGES_USERNAME }} | |
| - name: Build api image and start db and afp | |
| run: | | |
| set -euo pipefail | |
| compose=(docker compose -f docker-compose.yml) | |
| "${compose[@]}" build api | |
| "${compose[@]}" pull db | |
| "${compose[@]}" pull afp | |
| "${compose[@]}" up -d --wait db afp | |
| - name: Run pytest in api container | |
| id: pytest_in_container | |
| env: | |
| PYTEST_PATH_INPUT: ${{ inputs.test_path }} | |
| run: | | |
| set -euo pipefail | |
| compose=(docker compose -f docker-compose.yml) | |
| if [ -n "${PYTEST_PATH_INPUT:-}" ]; then | |
| pytest_target=${PYTEST_PATH_INPUT} | |
| else | |
| pytest_target="${API_DIR_NAME}/test/" | |
| fi | |
| pytest_target_q=$(printf '%q' "$pytest_target") | |
| inner_script=$( | |
| printf '%s\n' \ | |
| 'set -euo pipefail' \ | |
| 'bash scripts/setup-filesystem.sh' \ | |
| "bash scripts/wait-for-postgres-db.sh db ${DB_PORT} ${DB_CONNECTION_TEST_MAX_ATTEMPTS} ${DB_CONNECTION_TEST_SLEEP_INTERVAL}" \ | |
| "bash scripts/wait-for-afp.sh afp ${AFP_PORT} 12 5" \ | |
| 'bash scripts/init-django-data.sh' \ | |
| "pytest -v --tb=short -x --junitxml=test-results.xml ${pytest_target_q}" | |
| ) | |
| # Mount workspace so --junitxml=test-results.xml is visible on the runner (base compose has no ./ bind mount). | |
| # CI_STARTUP_TRACE: verbose Django/pytest startup diagnostics (see docs/workflows.md). | |
| # PYTHONFAULTHANDLER: on hang, SIGQUIT dumps Python stacks (docker kill -s QUIT <pytest_pid>). | |
| # PYTEST_PLUGINS: load startup plugin when bind-mount hides image-local *.egg-info entry points. | |
| # The thin entrypoint (exec "$@") passes the command straight through; CMD is overridden here with the | |
| # inner_script so the container runs pytest instead of the default start-server.sh. | |
| "${compose[@]}" run --rm -T \ | |
| -e CI_STARTUP_TRACE=1 \ | |
| -e PYTHONFAULTHANDLER=1 \ | |
| -e PYTEST_PLUGINS=api.ci_pytest_startup_plugin \ | |
| -v "${GITHUB_WORKSPACE}:/home/app" api bash -lc "$inner_script" | |
| - name: Show Compose service logs on failure | |
| if: failure() && steps.pytest_in_container.outcome == 'failure' | |
| run: | | |
| docker compose -f docker-compose.yml logs --no-color --tail=200 db afp || true | |
| - name: Publish Test Results | |
| uses: EnricoMi/publish-unit-test-result-action@v2.23.0 | |
| # Fork PRs: GITHUB_TOKEN is read-only; this action needs Checks API (and optional PR comments). | |
| if: >- | |
| always() && | |
| (github.event_name != 'pull_request' || | |
| github.event.pull_request.head.repo.full_name == github.repository) | |
| with: | |
| files: test-results.xml | |
| - name: Tear down Compose | |
| if: always() | |
| run: docker compose -f docker-compose.yml down -v || true |