name: CI on: push: branches: [main] pull_request: branches: [main] env: PYTHON_VERSION: '3.11' NODE_VERSION: '20' # Cancel in-progress runs for the same branch concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # ============================================================================ # Backend Checks # ============================================================================ backend-lint: name: Backend Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install ruff run: pip install ruff - name: Run ruff check run: ruff check backend/ - name: Run ruff format check run: ruff format --check backend/ backend-tests: name: Backend Tests runs-on: ubuntu-latest needs: backend-lint steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Cache pip uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-asyncio pytest-cov - name: Run tests timeout-minutes: 10 run: | cd backend python -m pytest tests/ -v --tb=short # ============================================================================ # Frontend Checks # ============================================================================ frontend-lint: name: Frontend Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Install dependencies working-directory: frontend run: npm ci - name: Run ESLint working-directory: frontend run: npm run lint frontend-typecheck: name: Frontend Type Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Install dependencies working-directory: frontend run: npm ci - name: Run TypeScript check working-directory: frontend run: npx tsc --noEmit frontend-tests: name: Frontend Tests runs-on: ubuntu-latest needs: [frontend-lint, frontend-typecheck] steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Install dependencies working-directory: frontend run: npm ci - name: Run tests timeout-minutes: 10 working-directory: frontend run: npm run test:run frontend-build: name: Frontend Build runs-on: ubuntu-latest needs: [frontend-tests] steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Install dependencies working-directory: frontend run: npm ci - name: Build working-directory: frontend run: npm run build # ============================================================================ # Docker Tests (matches test_docker.sh) # ============================================================================ docker-test: name: Docker Build runs-on: ubuntu-latest timeout-minutes: 20 needs: [backend-tests, frontend-build] steps: - uses: actions/checkout@v4 # Test 1: Docker Build - name: Build production image run: docker build -t bambuddy:test . - name: Verify backend imports run: docker run --rm bambuddy:test python -c "import backend.app.main; print('Backend imports OK')" - name: Verify static files exist run: docker run --rm bambuddy:test test -d /app/static # Test 2: Backend Unit Tests in Docker - name: Build backend test image run: docker compose -f docker-compose.test.yml build backend-test - name: Run backend tests in Docker run: docker compose -f docker-compose.test.yml run --rm backend-test # Test 3: Frontend Unit Tests in Docker - name: Build frontend test image run: docker compose -f docker-compose.test.yml build frontend-test - name: Run frontend tests in Docker run: docker compose -f docker-compose.test.yml run --rm frontend-test # Test 4: Integration Tests - name: Build integration container run: docker compose -f docker-compose.test.yml build integration - name: Start integration container run: | docker compose -f docker-compose.test.yml up -d integration echo "Waiting for container to be healthy..." for i in {1..30}; do if docker compose -f docker-compose.test.yml ps integration | grep -q "healthy"; then echo "Container is healthy" break fi sleep 2 done - name: Test health endpoint run: | HEALTH=$(docker compose -f docker-compose.test.yml exec -T integration curl -s http://localhost:8000/health) echo "$HEALTH" echo "$HEALTH" | grep -q "healthy" - name: Test API endpoint run: | docker compose -f docker-compose.test.yml exec -T integration curl -s http://localhost:8000/api/v1/settings - name: Test static files served run: | STATUS=$(docker compose -f docker-compose.test.yml exec -T integration curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/) echo "Static files HTTP status: $STATUS" [ "$STATUS" = "200" ] - name: Show logs on failure if: failure() run: docker compose -f docker-compose.test.yml logs - name: Cleanup if: always() run: docker compose -f docker-compose.test.yml down -v --remove-orphans