name: CI

on:
  push:
    branches: [main, dev]
  pull_request:
    branches: [main, dev]

# Cancel any in-flight CI run for the same branch / PR when a new push lands.
# Dependabot rebases and force-pushes used to fan out wasted runs per chore PR;
# this collapses that to one in-flight run per ref. Different branches / PRs
# don't cancel each other (the group key includes ref / PR number).
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        # CI currently only verifies 3.12. The lib supports 3.10+ in metadata but
        # a few async tests in tests/network/test_electrumx.py are timing-sensitive
        # on slower runners and fail on 3.10/3.11. To re-enable, fix the
        # TestResponseCorrelation tests to be timing-robust, then add the
        # versions back to this matrix.
        python-version: ["3.12"]
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405  # v6
        with:
          python-version: ${{ matrix.python-version }}
      # Cache Poetry's downloads + resolved virtualenv, keyed on the lock file,
      # so `poetry install --sync` skips re-downloading/rebuilding native wheels
      # on every run. A cache miss just falls back to a normal cold install.
      - name: Cache Poetry packages
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae  # v5.0.5
        with:
          path: ~/.cache/pypoetry
          key: poetry-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
          restore-keys: |
            poetry-${{ runner.os }}-py${{ matrix.python-version }}-
      - name: Install Poetry
        # Hash-pinned via ci/poetry-pin.txt. Closes the OpenSSF Scorecard
        # / CodeQL PinnedDependenciesID alert. See ci/README.md for how
        # to bump the pin.
        run: pip install -r ci/poetry-pin.txt --require-hashes
      - name: Install dependencies
        run: poetry install --sync --no-interaction
      # The full-suite coverage run below is the canonical pass/fail gate. A
      # separate non-coverage `pytest tests/` run was redundant (same tests) and
      # re-ran the slowest tests a second time, so it was dropped.
      - name: Security module coverage (must be 100%)
        run: poetry run pytest tests/security/ -o "addopts=" --cov=pyrxd.security --cov-fail-under=100
      - name: Tests + overall coverage (must be 85%)
        run: poetry run pytest tests/ -o "addopts=" --cov=pyrxd --cov-fail-under=85 --tb=short
      - name: Bandit security scan
        run: poetry run bandit -r src/ -c pyproject.toml
      - name: Type check
        run: poetry run mypy src/pyrxd/security/
