name: CI on: push: branches: [main, develop] pull_request: branches: [main, develop] jobs: # ============================================================================= # Backend Tests - PHP 8.5, PHPStan, PHPUnit # ============================================================================= test-backend: name: Backend Tests runs-on: ubuntu-latest defaults: run: working-directory: backend services: postgres: image: postgres:18.1-alpine env: POSTGRES_DB: classeo_test POSTGRES_USER: classeo POSTGRES_PASSWORD: classeo ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis:7.4-alpine ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.5' extensions: intl, pdo_pgsql, amqp, redis, zip coverage: xdebug - name: Get Composer cache directory id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --prefer-dist --no-progress - name: Run PHP CS Fixer (check) run: composer cs-check - name: Run PHPStan run: composer phpstan - name: Create test database and run migrations run: | php bin/console doctrine:database:create --if-not-exists --env=test php bin/console doctrine:migrations:migrate --no-interaction --env=test env: DATABASE_URL: postgresql://classeo:classeo@localhost:5432/classeo_test?serverVersion=18 REDIS_URL: redis://localhost:6379 - name: Run PHPUnit run: composer test env: DATABASE_URL: postgresql://classeo:classeo@localhost:5432/classeo_test?serverVersion=18 REDIS_URL: redis://localhost:6379 - name: Run BC Isolation Check working-directory: . run: ./scripts/check-bc-isolation.sh # ============================================================================= # Frontend Tests - Vitest, Playwright # ============================================================================= test-frontend: name: Frontend Tests runs-on: ubuntu-latest defaults: run: working-directory: frontend steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '22' - name: Setup pnpm uses: pnpm/action-setup@v3 with: version: 9 - name: Get pnpm store directory id: pnpm-cache run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm dependencies uses: actions/cache@v4 with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: ${{ runner.os }}-pnpm-store- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Run linter run: pnpm run lint - name: Run TypeScript check run: pnpm run check - name: Run unit tests run: pnpm run test # ============================================================================= # E2E Tests - Playwright with Docker backend # ============================================================================= test-e2e: name: E2E Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '22' - name: Setup pnpm uses: pnpm/action-setup@v3 with: version: 9 - name: Get pnpm store directory id: pnpm-cache run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm dependencies uses: actions/cache@v4 with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: ${{ runner.os }}-pnpm-store- - name: Install frontend dependencies working-directory: frontend run: pnpm install --frozen-lockfile - name: Install Playwright browsers working-directory: frontend run: pnpm exec playwright install --with-deps - name: Build and start backend services run: | # Build images first (with Docker layer caching) docker compose build php # Start services (includes db, redis, rabbitmq dependencies) # Use null mailer transport since mailpit is not available in CI # Use test environment to disable rate limiting for E2E tests APP_ENV=test MAILER_DSN="null://null" docker compose up -d php timeout-minutes: 10 - name: Wait for backend to be ready run: | echo "Waiting for backend to be ready (composer install + app startup)..." # Wait up to 5 minutes for the backend to respond # Using /api/docs which is a public endpoint (no auth required) timeout 300 bash -c 'until curl -sf http://localhost:18000/api/docs > /dev/null 2>&1; do echo "Waiting for backend..." sleep 5 done' echo "Backend is ready!" - name: Generate JWT keys for authentication run: | # Generate JWT keys if they don't exist (required for login/token endpoints) docker compose exec -T php php bin/console lexik:jwt:generate-keypair --skip-if-exists - name: Create test database and run migrations run: | # Create test database (Symfony adds _test suffix in test environment) docker compose exec -T php php bin/console doctrine:database:create --if-not-exists # Run migrations to create all tables (including audit_log) docker compose exec -T php php bin/console doctrine:migrations:migrate --no-interaction - name: Show backend logs on failure if: failure() run: docker compose logs php - name: Configure hosts for multi-tenant testing run: | echo "127.0.0.1 classeo.local" | sudo tee -a /etc/hosts echo "127.0.0.1 ecole-alpha.classeo.local" | sudo tee -a /etc/hosts echo "127.0.0.1 ecole-beta.classeo.local" | sudo tee -a /etc/hosts cat /etc/hosts - name: Reset rate limiter before E2E tests run: docker compose exec -T php php bin/console app:dev:reset-rate-limit - name: Run E2E tests working-directory: frontend run: pnpm run test:e2e env: # Frontend serves on 4173 (preview mode), backend on 18000 (Docker) PUBLIC_API_PORT: "18000" PUBLIC_API_URL: http://localhost:18000/api - name: Upload Playwright report uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: frontend/playwright-report/ retention-days: 7 - name: Stop backend services if: always() run: docker compose down # ============================================================================= # Naming Conventions Check # ============================================================================= check-naming: name: Naming Conventions runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Naming Check run: ./scripts/check-naming.sh # ============================================================================= # Build Check # ============================================================================= build: name: Build Check runs-on: ubuntu-latest needs: [test-backend, test-frontend, test-e2e] steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build backend image uses: docker/build-push-action@v6 with: context: ./backend push: false cache-from: type=gha cache-to: type=gha,mode=max - name: Build frontend image uses: docker/build-push-action@v6 with: context: ./frontend push: false cache-from: type=gha cache-to: type=gha,mode=max