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