name: Test Python Package on: push: branches: - main pull_request: jobs: unit-tests: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version-file: .python-version - name: Install uv run: pip install uv - name: Install dependencies env: UV_LINK_MODE: copy run: uv sync --all-extras - name: Run unit tests run: | # --cov-fail-under=0: partial coverage only; floor is checked in coverage-report. uv run pytest tests/unit/ -m "not integration" \ --cov=python_repositories \ --cov-report= \ --cov-fail-under=0 - name: Upload unit coverage uses: https://github.com/christopherHX/gitea-upload-artifact@v4 with: name: coverage-unit path: .coverage retention-days: 1 compression-level: 0 integration-tests: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version-file: .python-version - name: Install uv run: pip install uv - name: Install dependencies env: UV_LINK_MODE: copy run: uv sync --all-extras - name: Verify Docker run: docker info - name: Run integration tests run: | # --cov-fail-under=0: partial coverage only; floor is checked in coverage-report. uv run pytest -m integration \ --cov=python_repositories \ --cov-report= \ --cov-fail-under=0 - name: Upload integration coverage uses: https://github.com/christopherHX/gitea-upload-artifact@v4 with: name: coverage-integration path: .coverage retention-days: 1 compression-level: 0 coverage-report: # Merges unit + integration coverage and enforces fail_under from pyproject.toml. needs: [unit-tests, integration-tests] runs-on: ubuntu-latest if: github.event_name == 'pull_request' || github.event_name == 'push' steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version-file: .python-version - name: Install uv run: pip install uv - name: Install dependencies env: UV_LINK_MODE: copy run: uv sync --all-extras - name: Download unit coverage uses: https://github.com/christopherHX/gitea-download-artifact@v4 with: name: coverage-unit path: coverage-unit - name: Download integration coverage uses: https://github.com/christopherHX/gitea-download-artifact@v4 with: name: coverage-integration path: coverage-integration - name: Combine coverage report run: | # --fail-under=0 so the full report is always written before the floor check. uv run coverage combine coverage-unit/.coverage coverage-integration/.coverage uv run coverage report -m --include='python_repositories/*' --fail-under=0 > coverage.txt - name: Show coverage report run: cat coverage.txt - name: Enforce coverage floor run: | # Reads fail_under from pyproject.toml; only combined coverage is evaluated here. FAIL_UNDER=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['tool']['coverage']['report']['fail_under'])") echo "Checking combined coverage against ${FAIL_UNDER}% floor..." if uv run coverage report --fail-under="$FAIL_UNDER" --include='python_repositories/*'; then echo "Coverage floor met." else echo "::error::Combined coverage is below the ${FAIL_UNDER}% floor" exit 1 fi - name: Post coverage summary to PR if: github.event_name == 'pull_request' env: API_URL: ${{ vars.API_URL }} REPO_OWNER: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} PR_NUMBER: ${{ github.event.pull_request.number }} CI_RUNNER_TOKEN: ${{ secrets.CI_RUNNER_TOKEN }} run: | PAYLOAD=$(python3 -c ' import json import pathlib coverage = pathlib.Path("coverage.txt").read_text() print( json.dumps( { "body": f"**Test Coverage Report:**\n```\n{coverage}\n```", } ) ) ') curl -sf -X POST "$API_URL/repos/$REPO_OWNER/$REPO_NAME/issues/$PR_NUMBER/comments" \ -H "Authorization: token $CI_RUNNER_TOKEN" \ -H "Content-Type: application/json" \ -d "$PAYLOAD"