name: CI - Test & Build on: push: branches: [ main, develop, 'feature/**', 'claude/**' ] pull_request: branches: [ main, develop ] env: NODE_VERSION: '22' jobs: # ============================================ # Lint & Type Check # ============================================ lint: name: Lint & Type Check runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install root dependencies run: npm ci - name: Install frontend dependencies working-directory: frontend run: npm ci - name: Install backend dependencies working-directory: backend run: npm ci - name: Run lint gate run: npm run lint # ============================================ # Backend Unit Tests # ============================================ backend-tests: name: Backend Unit Tests runs-on: ubuntu-latest services: mariadb: image: mariadb:10.11 env: MYSQL_ROOT_PASSWORD: testpassword MYSQL_DATABASE: tilbudgivern_test MYSQL_USER: testuser MYSQL_PASSWORD: testpassword ports: - 3306:3306 options: >- --health-cmd="mysqladmin ping -h localhost" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install backend dependencies working-directory: backend run: npm ci - name: Wait for MariaDB run: | while ! mysqladmin ping -h"127.0.0.1" --silent; do sleep 1 done - name: Run backend tests working-directory: backend env: DB_HOST: 127.0.0.1 DB_PORT: 3306 DB_USER: testuser DB_PASSWORD: testpassword DB_NAME: tilbudgivern_test NODE_ENV: test run: npm test -- --coverage --passWithNoTests - name: Upload coverage report uses: actions/upload-artifact@v7 if: always() # Artifact upload must never fail the job (storage quota can be full) continue-on-error: true with: name: backend-coverage path: backend/coverage retention-days: 3 # ============================================ # Frontend Build # ============================================ frontend-build: name: Frontend Build runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install frontend dependencies working-directory: frontend run: npm ci - name: Build frontend working-directory: frontend env: CI: false # Prevent treating warnings as errors run: npm run build - name: Upload frontend build artifact uses: actions/upload-artifact@v7 # Artifact upload must never fail the job (storage quota can be full) continue-on-error: true with: name: frontend-build path: frontend/build retention-days: 3 # ============================================ # E2E Tests (Playwright) # ============================================ e2e-tests: name: E2E Tests (Playwright) runs-on: ubuntu-latest needs: [frontend-build, backend-tests] if: github.event_name == 'pull_request' services: mariadb: image: mariadb:10.11 env: MYSQL_ROOT_PASSWORD: testpassword MYSQL_DATABASE: tilbudgivern_test MYSQL_USER: testuser MYSQL_PASSWORD: testpassword ports: - 3306:3306 options: >- --health-cmd="mysqladmin ping -h localhost" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: | npm ci cd frontend && npm ci cd ../backend && npm ci cd ../tests && npm ci # Build in-job instead of downloading the frontend-build artifact - # artifact upload is best-effort and can be skipped on full quota - name: Build frontend working-directory: frontend env: CI: false run: npm run build - name: Install Playwright browsers working-directory: tests run: npx playwright install --with-deps chromium - name: Wait for MariaDB run: | while ! mysqladmin ping -h"127.0.0.1" --silent; do sleep 1 done - name: Start server env: DB_HOST: 127.0.0.1 DB_PORT: 3306 DB_USER: testuser DB_PASSWORD: testpassword DB_NAME: tilbudgivern_test PORT: 4032 NODE_ENV: test JWT_ACCESS_SECRET: ci-only-access-secret-not-for-production JWT_REFRESH_SECRET: ci-only-refresh-secret-not-for-production AUTH_USERNAME: ci-test-user AUTH_PASSWORD: ci-test-password ORDRESTYRING_API_TOKEN: ci-test-token-no-network-use ORDRESTYRING_AUTO_SYNC_DISABLED: true run: | cd backend && node unified-server.js & sleep 10 curl -f http://localhost:4032/api/health || exit 1 - name: Run blocking carpenter smoke test working-directory: tests env: PLAYWRIGHT_BASE_URL: http://localhost:4032 PLAYWRIGHT_USERNAME: ci-test-user PLAYWRIGHT_PASSWORD: ci-test-password run: npx playwright test full-roof-quote-flow.spec.js --project=chromium --reporter=list - name: Run extended Playwright tests working-directory: tests env: PLAYWRIGHT_BASE_URL: http://localhost:4032 PLAYWRIGHT_USERNAME: ci-test-user PLAYWRIGHT_PASSWORD: ci-test-password run: npx playwright test --project=chromium --reporter=html --grep-invert "full carpenter roof quote smoke flow" continue-on-error: true - name: Upload Playwright report uses: actions/upload-artifact@v7 if: always() continue-on-error: true with: name: playwright-report path: tests/playwright-report retention-days: 3 - name: Upload Playwright screenshots uses: actions/upload-artifact@v7 if: failure() continue-on-error: true with: name: playwright-screenshots path: tests/test-results retention-days: 3 # ============================================ # Security Scan # ============================================ security-scan: name: Security Scan runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: | npm ci cd frontend && npm ci cd ../backend && npm ci - name: Run npm audit (frontend) working-directory: frontend run: npm audit --audit-level=high continue-on-error: true - name: Run npm audit (backend) working-directory: backend run: npm audit --audit-level=high continue-on-error: true # ============================================ # Summary Job # ============================================ ci-summary: name: CI Summary runs-on: ubuntu-latest needs: [lint, backend-tests, frontend-build, security-scan, e2e-tests] if: always() steps: - name: Check CI status run: | if [[ "${{ needs.lint.result }}" == "failure" ]] || \ [[ "${{ needs.backend-tests.result }}" == "failure" ]] || \ [[ "${{ needs.frontend-build.result }}" == "failure" ]] || \ [[ "${{ needs.e2e-tests.result }}" == "failure" ]]; then echo "CI failed!" exit 1 fi echo "CI passed successfully!"