ci.yml 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. name: CI
  2. on:
  3. push:
  4. branches: [main]
  5. pull_request:
  6. branches: [main]
  7. env:
  8. PYTHON_VERSION: '3.11'
  9. NODE_VERSION: '20'
  10. # Cancel in-progress runs for the same branch
  11. concurrency:
  12. group: ${{ github.workflow }}-${{ github.ref }}
  13. cancel-in-progress: true
  14. jobs:
  15. # ============================================================================
  16. # Backend Checks
  17. # ============================================================================
  18. backend-lint:
  19. name: Backend Lint
  20. runs-on: ubuntu-latest
  21. steps:
  22. - uses: actions/checkout@v4
  23. - name: Set up Python
  24. uses: actions/setup-python@v5
  25. with:
  26. python-version: ${{ env.PYTHON_VERSION }}
  27. - name: Install ruff
  28. run: pip install ruff
  29. - name: Run ruff check
  30. run: ruff check backend/
  31. - name: Run ruff format check
  32. run: ruff format --check backend/
  33. backend-tests:
  34. name: Backend Tests
  35. runs-on: ubuntu-latest
  36. needs: backend-lint
  37. steps:
  38. - uses: actions/checkout@v4
  39. - name: Set up Python
  40. uses: actions/setup-python@v5
  41. with:
  42. python-version: ${{ env.PYTHON_VERSION }}
  43. - name: Cache pip
  44. uses: actions/cache@v4
  45. with:
  46. path: ~/.cache/pip
  47. key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
  48. restore-keys: |
  49. ${{ runner.os }}-pip-
  50. - name: Install dependencies
  51. run: |
  52. python -m pip install --upgrade pip
  53. pip install -r requirements.txt
  54. pip install pytest pytest-asyncio pytest-cov pytest-timeout
  55. - name: Run tests
  56. timeout-minutes: 10
  57. run: |
  58. cd backend
  59. python -m pytest tests/ -v --tb=short --timeout=60 --timeout-method=thread
  60. # ============================================================================
  61. # Frontend Checks
  62. # ============================================================================
  63. frontend-lint:
  64. name: Frontend Lint
  65. runs-on: ubuntu-latest
  66. steps:
  67. - uses: actions/checkout@v4
  68. - name: Set up Node.js
  69. uses: actions/setup-node@v4
  70. with:
  71. node-version: ${{ env.NODE_VERSION }}
  72. cache: 'npm'
  73. cache-dependency-path: frontend/package-lock.json
  74. - name: Install dependencies
  75. working-directory: frontend
  76. run: npm ci
  77. - name: Run ESLint
  78. working-directory: frontend
  79. run: npm run lint
  80. frontend-typecheck:
  81. name: Frontend Type Check
  82. runs-on: ubuntu-latest
  83. steps:
  84. - uses: actions/checkout@v4
  85. - name: Set up Node.js
  86. uses: actions/setup-node@v4
  87. with:
  88. node-version: ${{ env.NODE_VERSION }}
  89. cache: 'npm'
  90. cache-dependency-path: frontend/package-lock.json
  91. - name: Install dependencies
  92. working-directory: frontend
  93. run: npm ci
  94. - name: Run TypeScript check
  95. working-directory: frontend
  96. run: npx tsc --noEmit
  97. frontend-tests:
  98. name: Frontend Tests
  99. runs-on: ubuntu-latest
  100. needs: [frontend-lint, frontend-typecheck]
  101. steps:
  102. - uses: actions/checkout@v4
  103. - name: Set up Node.js
  104. uses: actions/setup-node@v4
  105. with:
  106. node-version: ${{ env.NODE_VERSION }}
  107. cache: 'npm'
  108. cache-dependency-path: frontend/package-lock.json
  109. - name: Install dependencies
  110. working-directory: frontend
  111. run: npm ci
  112. - name: Run tests
  113. timeout-minutes: 10
  114. working-directory: frontend
  115. run: npm run test:run
  116. frontend-build:
  117. name: Frontend Build
  118. runs-on: ubuntu-latest
  119. needs: [frontend-tests]
  120. steps:
  121. - uses: actions/checkout@v4
  122. - name: Set up Node.js
  123. uses: actions/setup-node@v4
  124. with:
  125. node-version: ${{ env.NODE_VERSION }}
  126. cache: 'npm'
  127. cache-dependency-path: frontend/package-lock.json
  128. - name: Install dependencies
  129. working-directory: frontend
  130. run: npm ci
  131. - name: Build
  132. working-directory: frontend
  133. run: npm run build
  134. # ============================================================================
  135. # Docker Tests (matches test_docker.sh)
  136. # ============================================================================
  137. docker-test:
  138. name: Docker Build
  139. runs-on: ubuntu-latest
  140. timeout-minutes: 20
  141. needs: [backend-tests, frontend-build]
  142. steps:
  143. - uses: actions/checkout@v4
  144. # Test 1: Docker Build
  145. - name: Build production image
  146. run: docker build -t bambuddy:test .
  147. - name: Verify backend imports
  148. run: docker run --rm bambuddy:test python -c "import backend.app.main; print('Backend imports OK')"
  149. - name: Verify static files exist
  150. run: docker run --rm bambuddy:test test -d /app/static
  151. # Test 2: Backend Unit Tests in Docker
  152. - name: Build backend test image
  153. run: docker compose -f docker-compose.test.yml build backend-test
  154. - name: Run backend tests in Docker
  155. run: docker compose -f docker-compose.test.yml run --rm backend-test
  156. # Test 3: Frontend Unit Tests in Docker
  157. - name: Build frontend test image
  158. run: docker compose -f docker-compose.test.yml build frontend-test
  159. - name: Run frontend tests in Docker
  160. run: docker compose -f docker-compose.test.yml run --rm frontend-test
  161. # Test 4: Integration Tests
  162. - name: Build integration container
  163. run: docker compose -f docker-compose.test.yml build integration
  164. - name: Start integration container
  165. run: |
  166. docker compose -f docker-compose.test.yml up -d integration
  167. echo "Waiting for container to be healthy..."
  168. for i in {1..30}; do
  169. if docker compose -f docker-compose.test.yml ps integration | grep -q "healthy"; then
  170. echo "Container is healthy"
  171. break
  172. fi
  173. sleep 2
  174. done
  175. - name: Test health endpoint
  176. run: |
  177. HEALTH=$(docker compose -f docker-compose.test.yml exec -T integration curl -s http://localhost:8000/health)
  178. echo "$HEALTH"
  179. echo "$HEALTH" | grep -q "healthy"
  180. - name: Test API endpoint
  181. run: |
  182. docker compose -f docker-compose.test.yml exec -T integration curl -s http://localhost:8000/api/v1/settings
  183. - name: Test static files served
  184. run: |
  185. STATUS=$(docker compose -f docker-compose.test.yml exec -T integration curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/)
  186. echo "Static files HTTP status: $STATUS"
  187. [ "$STATUS" = "200" ]
  188. - name: Show logs on failure
  189. if: failure()
  190. run: docker compose -f docker-compose.test.yml logs
  191. - name: Cleanup
  192. if: always()
  193. run: docker compose -f docker-compose.test.yml down -v --remove-orphans