Files
goodgo-platform/.github/workflows/ci.yml
2026-05-04 18:34:41 +07:00

225 lines
5.7 KiB
YAML

name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
name: Lint → Typecheck → Test → Build
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22]
services:
postgres:
image: postgis/postgis:16-3.4
env:
POSTGRES_DB: goodgo_test
POSTGRES_USER: goodgo
POSTGRES_PASSWORD: goodgo_test_secret
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U goodgo -d goodgo_test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
--health-start-period 30s
env:
DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test
NODE_ENV: test
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate Prisma client
run: pnpm db:generate
- name: Lint
run: pnpm lint
- name: Typecheck
run: pnpm typecheck
- name: Test
run: pnpm test
- name: Build
run: pnpm build
ai-services:
name: AI Services (Python) — Smoke
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: libs/ai-services
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
cache-dependency-path: libs/ai-services/pyproject.toml
- name: Install dependencies (runtime + dev, no underthesea)
run: |
python -m pip install --upgrade pip
pip install \
"fastapi==0.115.0" \
"uvicorn[standard]==0.32.0" \
"xgboost==2.1.0" \
"numpy==1.26.4" \
"pydantic==2.9.0" \
"pydantic-settings==2.5.0" \
"httpx==0.27.0" \
"slowapi==0.1.9" \
"scikit-learn>=1.5.0" \
"pytest>=8.3.0" \
"pytest-asyncio>=0.24.0"
- name: Pytest (unit + health smoke)
env:
AI_CORS_ORIGINS: http://localhost:3000
run: pytest -q --ignore=tests/test_nlp.py
- name: Boot FastAPI + /health smoke
env:
AI_CORS_ORIGINS: http://localhost:3000
run: |
uvicorn app.main:app --host 127.0.0.1 --port 8000 &
PID=$!
for i in 1 2 3 4 5 6 7 8 9 10; do
if curl -sf http://127.0.0.1:8000/health; then
echo "health ok"
kill $PID
exit 0
fi
sleep 2
done
echo "health failed"
kill $PID || true
exit 1
- name: OpenAPI schema export (verifies /predict routes)
env:
AI_CORS_ORIGINS: http://localhost:3000
run: |
python - <<'PY'
import json, sys
from app.main import app
schema = app.openapi()
paths = schema.get("paths", {})
required = ["/avm/predict", "/avm/v2/predict", "/avm/industrial/predict", "/moderation/check", "/neighborhood/score"]
missing = [p for p in required if p not in paths]
if missing:
print("MISSING OpenAPI paths:", missing)
sys.exit(1)
print("OpenAPI paths OK:", sorted(paths.keys()))
PY
e2e:
name: E2E Tests
needs: ci
runs-on: ubuntu-latest
timeout-minutes: 45
env:
CI: true
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Load E2E environment
run: awk 'NF && $1 !~ /^#/' .env.test >> "$GITHUB_ENV"
- name: Start CI service stack
run: docker compose --env-file .env.ci -f docker-compose.ci.yml up -d --wait
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
- name: Install Playwright system deps
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps chromium
- name: Generate Prisma client
run: pnpm db:generate
- name: Run database migrations
run: pnpm db:migrate:deploy
- name: Seed database
run: pnpm db:seed
- name: Run E2E tests
run: pnpm test:e2e
- name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 14
- name: Upload Playwright traces
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-traces
path: test-results/
retention-days: 7
- name: Stop CI service stack
if: always()
run: docker compose --env-file .env.ci -f docker-compose.ci.yml down -v