name: CI on: push: branches: [main] pull_request: branches: [main] jobs: # --------------------------------------------------------------------------- # Job 1: Backend — lint + type-check + pytest # --------------------------------------------------------------------------- backend: name: Backend Tests runs-on: ubuntu-latest services: postgres: image: pgvector/pgvector:pg16 env: POSTGRES_DB: konstruct POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: >- --health-cmd "pg_isready -U postgres" --health-interval 5s --health-timeout 5s --health-retries 10 ports: - 5432:5432 redis: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 5s --health-retries 10 ports: - 6379:6379 env: DATABASE_URL: postgresql+asyncpg://konstruct_app:konstruct_pass@localhost:5432/konstruct DATABASE_ADMIN_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres REDIS_URL: redis://localhost:6379/0 steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python 3.12 uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install uv run: pip install uv - name: Install dependencies run: uv sync - name: Lint — ruff check run: uv run ruff check packages/ tests/ - name: Lint — ruff format check run: uv run ruff format --check packages/ tests/ - name: Run pytest run: uv run pytest tests/ -x --tb=short --junitxml=test-results.xml - name: Upload pytest results if: always() uses: actions/upload-artifact@v4 with: name: pytest-results path: test-results.xml retention-days: 30 # --------------------------------------------------------------------------- # Job 2: Portal — build + E2E + Lighthouse CI # Depends on backend passing (fail-fast) # --------------------------------------------------------------------------- portal: name: Portal E2E runs-on: ubuntu-latest needs: backend services: postgres: image: pgvector/pgvector:pg16 env: POSTGRES_DB: konstruct POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: >- --health-cmd "pg_isready -U postgres" --health-interval 5s --health-timeout 5s --health-retries 10 ports: - 5432:5432 redis: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 5s --health-retries 10 ports: - 6379:6379 env: DATABASE_URL: postgresql+asyncpg://konstruct_app:konstruct_pass@localhost:5432/konstruct DATABASE_ADMIN_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres REDIS_URL: redis://localhost:6379/0 LLM_POOL_URL: http://localhost:8004 NEXT_PUBLIC_API_URL: http://localhost:8001 steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Node.js 22 uses: actions/setup-node@v4 with: node-version: "22" cache: "npm" cache-dependency-path: packages/portal/package-lock.json - name: Set up Python 3.12 uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install portal dependencies working-directory: packages/portal run: npm ci - name: Build portal (Next.js standalone) working-directory: packages/portal env: NEXT_PUBLIC_API_URL: http://localhost:8001 run: npm run build - name: Copy standalone assets working-directory: packages/portal run: | cp -r .next/static .next/standalone/.next/static cp -r public .next/standalone/public - name: Install Playwright browsers working-directory: packages/portal run: npx playwright install --with-deps chromium firefox webkit - name: Install Python dependencies and run migrations env: DATABASE_URL: postgresql+asyncpg://konstruct_app:konstruct_pass@localhost:5432/konstruct DATABASE_ADMIN_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres REDIS_URL: redis://localhost:6379/0 run: | pip install uv uv sync uv run alembic upgrade head uv run python -c "from shared.db import seed_admin; import asyncio; asyncio.run(seed_admin())" || true - name: Start gateway (background) env: DATABASE_URL: postgresql+asyncpg://konstruct_app:konstruct_pass@localhost:5432/konstruct DATABASE_ADMIN_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres REDIS_URL: redis://localhost:6379/0 LLM_POOL_URL: http://localhost:8004 run: | uv run uvicorn gateway.main:app --host 0.0.0.0 --port 8001 & - name: Wait for gateway to be ready run: timeout 30 bash -c 'until curl -sf http://localhost:8001/health; do sleep 1; done' - name: Run E2E flow + accessibility tests working-directory: packages/portal env: CI: "true" PLAYWRIGHT_BASE_URL: http://localhost:3000 API_URL: http://localhost:8001 AUTH_SECRET: ${{ secrets.AUTH_SECRET }} E2E_ADMIN_EMAIL: ${{ secrets.E2E_ADMIN_EMAIL }} E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }} E2E_CADMIN_EMAIL: ${{ secrets.E2E_CADMIN_EMAIL }} E2E_CADMIN_PASSWORD: ${{ secrets.E2E_CADMIN_PASSWORD }} E2E_OPERATOR_EMAIL: ${{ secrets.E2E_OPERATOR_EMAIL }} E2E_OPERATOR_PASSWORD: ${{ secrets.E2E_OPERATOR_PASSWORD }} run: npx playwright test e2e/flows/ e2e/accessibility/ - name: Run Lighthouse CI working-directory: packages/portal env: LHCI_BUILD_CONTEXT__CURRENT_HASH: ${{ github.sha }} run: npx lhci autorun --config=e2e/lighthouse/lighthouserc.json - name: Upload Playwright HTML report if: always() uses: actions/upload-artifact@v4 with: name: playwright-report path: packages/portal/playwright-report/ retention-days: 30 - name: Upload Playwright JUnit results if: always() uses: actions/upload-artifact@v4 with: name: playwright-junit path: packages/portal/playwright-results.xml retention-days: 30 - name: Upload Lighthouse report if: always() uses: actions/upload-artifact@v4 with: name: lighthouse-report path: packages/portal/.lighthouseci/ retention-days: 30