feat(pipeline): form_fields step — guaranteed accessible HTML on form pages #253
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| # Cancel in-progress runs for the same branch/PR | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| PYTHON_VERSION: "3.11" | |
| jobs: | |
| # ============================================================================ | |
| # Stage 1: Fast feedback (< 2 min) | |
| # ============================================================================ | |
| unit-tests: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Free disk space | |
| run: | | |
| sudo rm -rf /opt/hostedtoolcache/CodeQL /opt/hostedtoolcache/go \ | |
| /opt/hostedtoolcache/Ruby /usr/share/dotnet /usr/local/lib/android \ | |
| /usr/local/share/boost /usr/local/.ghcup /opt/ghc /usr/share/swift & | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Cache uv | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/uv | |
| key: uv-${{ runner.os }}-${{ hashFiles('pyproject.toml') }} | |
| restore-keys: uv-${{ runner.os }}- | |
| - name: Install uv and dependencies | |
| run: | | |
| pip install uv | |
| uv sync --all-extras | |
| - name: Run unit tests | |
| env: | |
| ANTHROPIC_API_KEY: test-key-for-ci | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_DEFAULT_REGION: us-east-1 | |
| run: | | |
| uv run pytest tests/unit \ | |
| -v \ | |
| --tb=short \ | |
| -m "unit" \ | |
| --cov=src \ | |
| --cov-report=term-missing \ | |
| --cov-report=xml \ | |
| --maxfail=5 \ | |
| --durations=10 | |
| - name: Upload coverage | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: unit-coverage | |
| path: coverage.xml | |
| retention-days: 7 | |
| # ============================================================================ | |
| # Stage 2: Integration tests with real services (< 5 min) | |
| # ============================================================================ | |
| integration-tests: | |
| name: Integration Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: unit-tests | |
| services: | |
| redis: | |
| image: redis:7-alpine | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| floci: | |
| image: hectorvent/floci:1.5.3 | |
| env: | |
| FLOCI_DEFAULT_REGION: us-east-1 | |
| options: >- | |
| --health-cmd "curl -sf http://localhost:4566/ || exit 1" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| ports: | |
| - 4566:4566 | |
| steps: | |
| - name: Free disk space | |
| run: | | |
| sudo rm -rf /opt/hostedtoolcache/CodeQL /opt/hostedtoolcache/go \ | |
| /opt/hostedtoolcache/Ruby /usr/share/dotnet /usr/local/lib/android \ | |
| /usr/local/share/boost /usr/local/.ghcup /opt/ghc /usr/share/swift & | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Cache uv | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/uv | |
| key: uv-${{ runner.os }}-${{ hashFiles('pyproject.toml') }} | |
| restore-keys: uv-${{ runner.os }}- | |
| - name: Install uv and dependencies | |
| run: | | |
| pip install uv | |
| uv sync --all-extras | |
| - name: Initialize S3 buckets on Floci | |
| # GitHub Actions service containers can't run init sidecars, so the | |
| # same init-aws.sh that docker-compose.dev.yml uses is invoked here | |
| # against the floci service container. AWS_ENDPOINT_URL routes every | |
| # `aws` command to Floci; see infrastructure/floci/init-aws.sh. | |
| env: | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_DEFAULT_REGION: us-east-1 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| run: | | |
| pip install awscli | |
| bash infrastructure/floci/init-aws.sh | |
| - name: Run integration tests | |
| env: | |
| ANTHROPIC_API_KEY: test-key-for-ci | |
| AWS_ACCESS_KEY_ID: test | |
| AWS_SECRET_ACCESS_KEY: test | |
| AWS_DEFAULT_REGION: us-east-1 | |
| AWS_ENDPOINT_URL: http://localhost:4566 | |
| REDIS_URL: redis://localhost:6379 | |
| SKIP_BEDROCK_TESTS: "1" | |
| run: | | |
| uv run pytest tests/integration \ | |
| -v \ | |
| --tb=short \ | |
| -m "integration" \ | |
| --cov=src \ | |
| --cov-report=term-missing \ | |
| --cov-report=xml \ | |
| --maxfail=5 \ | |
| --durations=10 | |
| - name: Upload coverage | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: integration-coverage | |
| path: coverage.xml | |
| retention-days: 7 | |
| # ============================================================================ | |
| # Stage 3: E2E tests - full Docker stack (< 10 min) | |
| # Only runs on PRs to main and pushes to main | |
| # ============================================================================ | |
| e2e-tests: | |
| name: E2E Tests (Docker) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| needs: integration-tests | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Free disk space | |
| run: | | |
| sudo rm -rf /opt/hostedtoolcache /usr/share/dotnet /usr/local/lib/android \ | |
| /usr/local/share/boost /usr/local/.ghcup /opt/ghc /usr/share/swift \ | |
| /usr/local/share/chromium /usr/local/share/powershell /usr/local/graalvm \ | |
| /usr/local/julia* /usr/local/sqlpackage /opt/pipx | |
| sudo apt-get clean || true | |
| docker system prune -af --volumes || true | |
| df -h / | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Cache Docker layers | |
| uses: actions/cache@v4 | |
| with: | |
| path: /tmp/.buildx-cache | |
| key: docker-${{ runner.os }}-${{ hashFiles('Dockerfile', 'pyproject.toml', 'uv.lock') }} | |
| restore-keys: docker-${{ runner.os }}- | |
| - name: Create .env file | |
| run: cp .env.example .env | |
| - name: Build Docker image with cache | |
| run: | | |
| docker buildx build \ | |
| --file Dockerfile \ | |
| --target development \ | |
| --cache-from type=local,src=/tmp/.buildx-cache \ | |
| --cache-to type=local,dest=/tmp/.buildx-cache-new,mode=max \ | |
| --load \ | |
| -t equalify-reflow:ci \ | |
| . | |
| - name: Rotate buildx cache | |
| run: | | |
| rm -rf /tmp/.buildx-cache | |
| mv /tmp/.buildx-cache-new /tmp/.buildx-cache | |
| - name: Start services | |
| run: | | |
| docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d --no-build | |
| - name: Wait for services | |
| run: | | |
| echo "Waiting for API Gateway..." | |
| timeout 90 bash -c 'until docker compose -f docker-compose.yml -f docker-compose.ci.yml exec -T api-gateway echo "ready" 2>/dev/null; do sleep 3; done' | |
| - name: Run E2E tests | |
| run: | | |
| docker compose -f docker-compose.yml -f docker-compose.ci.yml exec -T \ | |
| -e SKIP_BEDROCK_TESTS=1 \ | |
| api-gateway \ | |
| uv run pytest tests/e2e \ | |
| -v \ | |
| --tb=short \ | |
| -m "slow" \ | |
| --maxfail=3 \ | |
| --durations=20 | |
| - name: Collect logs on failure | |
| if: failure() | |
| run: | | |
| docker compose -f docker-compose.yml -f docker-compose.ci.yml logs > docker-logs.txt 2>&1 | |
| - name: Upload logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: docker-logs | |
| path: docker-logs.txt | |
| retention-days: 7 | |
| - name: Stop services | |
| if: always() | |
| run: docker compose -f docker-compose.yml -f docker-compose.ci.yml down -v | |
| # ============================================================================ | |
| # Final status check for branch protection | |
| # ============================================================================ | |
| ci-success: | |
| name: CI Success | |
| runs-on: ubuntu-latest | |
| needs: [unit-tests, integration-tests, e2e-tests] | |
| if: always() | |
| steps: | |
| - name: Check all jobs passed | |
| run: | | |
| if [[ "${{ needs.unit-tests.result }}" != "success" ]] || \ | |
| [[ "${{ needs.integration-tests.result }}" != "success" ]] || \ | |
| [[ "${{ needs.e2e-tests.result }}" != "success" ]]; then | |
| echo "::error::One or more CI jobs failed" | |
| exit 1 | |
| fi | |
| echo "All CI jobs passed successfully" |