name: Verify on: [push, pull_request] permissions: contents: read jobs: verify: runs-on: ubuntu-latest services: postgres: image: postgres:17 env: POSTGRES_PASSWORD: ci-only-password POSTGRES_DB: traceiq options: >- --label traceiq-ci-postgres=${{ github.repository }}:${{ github.run_id }}:${{ github.run_attempt }} --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10 env: E2E_PASSWORD: CI-Test-Only-SecurePassword882! PYTHONPATH: . steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v6 with: version: '0.10.12' - uses: actions/setup-node@v4 with: node-version: '22' cache: npm - run: uv sync --frozen --python 3.12 # runner 使用共享 Docker 网络;按本次运行的标签找服务 IP,避免共用 postgres 别名。 # act_runner v0.6.1 尚未提供 job.services.postgres.id,现有 Docker socket 仅用于读取。 - name: Resolve isolated PostgreSQL service env: POSTGRES_SERVICE_LABEL: traceiq-ci-postgres=${{ github.repository }}:${{ github.run_id }}:${{ github.run_attempt }} run: | postgres_containers=$(curl --fail --silent --show-error \ --unix-socket /var/run/docker.sock \ --get --data-urlencode "filters={\"label\":[\"$POSTGRES_SERVICE_LABEL\"]}" \ http://localhost/containers/json) POSTGRES_CONTAINERS="$postgres_containers" uv run python - <<'PY' import ipaddress import json import os containers = json.loads(os.environ["POSTGRES_CONTAINERS"]) if len(containers) != 1: raise SystemExit("Expected exactly one PostgreSQL service for this run") networks = list(containers[0]["NetworkSettings"]["Networks"].values()) if len(networks) != 1: raise SystemExit("Expected PostgreSQL to use the runner's single Docker network") address = ipaddress.IPv4Address(networks[0]["IPAddress"]) with open(os.environ["GITHUB_ENV"], "a") as env_file: env_file.write(f"DATABASE_URL=postgresql://postgres:ci-only-password@{address}:5432/traceiq\n") PY - run: npm ci --registry=https://registry.npmjs.org - run: uv run ruff check server finance tests scripts - run: uv run ruff format --check server finance tests scripts - run: uv run python manage.py makemigrations --check --dry-run - run: uv run pytest --cov=finance --cov-report=term-missing --cov-fail-under=85 - run: npm run build # 审计端点在 runner 网络下偶发 TLS 断开(npm error audit endpoint returned # an error),重试区分网络抖动与真实漏洞:漏洞是确定性的,重试后照样红。 - name: npm audit (production deps) run: | for attempt in 1 2 3; do npm audit --omit=dev --registry=https://registry.npmjs.org && exit 0 echo "::warning::npm audit 第 ${attempt} 次失败,15 秒后重试" sleep 15 done exit 1 - name: pip-audit run: | for attempt in 1 2 3; do uv run pip-audit && exit 0 echo "::warning::pip-audit 第 ${attempt} 次失败,15 秒后重试" sleep 15 done exit 1 - run: uv run python manage.py migrate --noinput - run: uv run python manage.py collectstatic --noinput - run: uv run python manage.py bootstrap --username admin --organization CI env: BOOTSTRAP_PASSWORD: ${{ env.E2E_PASSWORD }} # runner 出网对明文 HTTP(80 端口)一律 502,archive/security.ubuntu.com # 的 Ubuntu 源拉不到包;HTTPS 仓库(microsoft、launchpad 等)均正常。 # 装 Playwright 系统依赖前把 Ubuntu 源整体切到 HTTPS,官方源优先、镜像兜底。 - name: Switch apt sources to HTTPS run: | set -euo pipefail if [ "$(id -u)" -eq 0 ]; then S=""; else S="sudo"; fi sources=$(ls /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true) [ -n "$sources" ] || { echo "未找到 apt 源文件" >&2; exit 1; } backup=/tmp/apt-sources-orig rm -rf "$backup" && mkdir -p "$backup" $S cp -a /etc/apt/sources.list "$backup/" 2>/dev/null || true $S cp -a /etc/apt/sources.list.d "$backup/" restore() { $S rm -f /etc/apt/sources.list $S find /etc/apt/sources.list.d -mindepth 1 -maxdepth 1 -exec rm -rf {} + $S cp -a "$backup/sources.list" /etc/apt/sources.list 2>/dev/null || true $S cp -a "$backup/sources.list.d/." /etc/apt/sources.list.d/ } for mirror in \ "${CI_APT_MIRROR:-https://archive.ubuntu.com/ubuntu/}" \ https://mirrors.aliyun.com/ubuntu/ \ https://mirrors.tuna.tsinghua.edu.cn/ubuntu/; do echo "::group::apt 源 -> $mirror" restore $S sed -E -i "s#https?://([a-z0-9-]+\.)*(archive|security)\.ubuntu\.com/ubuntu/?#$mirror#g" $sources if $S apt-get update -o Acquire::Retries=3; then echo "apt 源已使用 $mirror" exit 0 fi echo "::warning::apt 源 $mirror 更新失败,尝试下一个镜像" done echo "所有 apt 镜像均不可用" >&2 exit 1 - name: Install Playwright Chromium run: | for attempt in 1 2 3; do npx playwright install --with-deps chromium && exit 0 if [ "$attempt" -lt 3 ]; then echo "::warning::Playwright 安装第 ${attempt} 次失败,15 秒后重试" sleep 15 fi done exit 1 - name: Browser regression shell: bash run: | uv run python manage.py runserver 127.0.0.1:8000 --noreload > /tmp/web.log 2>&1 & web=$! uv run python manage.py import_worker > /tmp/worker.log 2>&1 & worker=$! trap 'kill "$web" "$worker"' EXIT for i in {1..30}; do curl -fsS http://127.0.0.1:8000/api/health && break; sleep 1; done npm run test:e2e # Gitea 不支持 upload-artifact@v4 的 artifact 协议(GHESNotSupportedError), # 上传失败不该把已经全绿的测试标红;迁到 GitHub 托管时此步骤自动生效。 - uses: actions/upload-artifact@v4 if: always() continue-on-error: true with: name: browser-results path: | playwright-report test-results