Refactoring and testing #32
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| jobs: | |
| backend-lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Install dependencies | |
| working-directory: backend | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Run linting | |
| working-directory: backend | |
| run: | | |
| pip install ruff | |
| ruff check . --output-format=concise | |
| simulator-lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Install dependencies | |
| working-directory: simulator | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Run linting | |
| working-directory: simulator | |
| run: | | |
| pip install ruff | |
| ruff check . --output-format=concise | |
| simulator-tests: | |
| runs-on: ubuntu-latest | |
| needs: [backend-tests] | |
| services: | |
| postgres: | |
| image: postgres:16 | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: postgres | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| redis: | |
| image: redis:7-alpine | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| keycloak: | |
| image: quay.io/keycloak/keycloak:25.0 | |
| env: | |
| KEYCLOAK_ADMIN: admin | |
| KEYCLOAK_ADMIN_PASSWORD: admin | |
| KC_HEALTH_ENABLED: true | |
| KC_METRICS_ENABLED: true | |
| KC_DB: dev-file | |
| options: >- | |
| --health-cmd "curl -f http://localhost:8080/health/ready || exit 1" | |
| --health-interval 10s | |
| --health-timeout 10s | |
| --health-retries 30 | |
| --health-start-period 40s | |
| --entrypoint "/bin/sh" -c "/opt/keycloak/bin/kc.sh start-dev" | |
| ports: | |
| - 8080:8080 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Install backend dependencies | |
| working-directory: backend | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Install simulator dependencies | |
| working-directory: simulator | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Run database migrations | |
| working-directory: backend | |
| env: | |
| DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres | |
| REDIS_URL: redis://localhost:6379 | |
| run: | | |
| pip install alembic | |
| alembic upgrade head | |
| - name: Wait for Keycloak | |
| run: | | |
| echo "Waiting for Keycloak to be ready..." | |
| timeout 120 bash -c 'until curl -f -s http://localhost:8080/health/ready; do sleep 2; done' | |
| echo "Keycloak is ready" | |
| - name: Setup Keycloak realm | |
| run: | | |
| echo "Keycloak is ready, waiting a bit more for admin API..." | |
| sleep 10 | |
| echo "Getting admin token..." | |
| TOKEN=$(curl -s -X POST http://localhost:8080/realms/master/protocol/openid-connect/token \ | |
| -d "client_id=admin-cli" \ | |
| -d "username=admin" \ | |
| -d "password=admin" \ | |
| -d "grant_type=password" | python3 -c "import sys, json; print(json.load(sys.stdin).get('access_token', ''))") | |
| if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then | |
| echo "Failed to get Keycloak admin token, retrying..." | |
| sleep 5 | |
| TOKEN=$(curl -s -X POST http://localhost:8080/realms/master/protocol/openid-connect/token \ | |
| -d "client_id=admin-cli" \ | |
| -d "username=admin" \ | |
| -d "password=admin" \ | |
| -d "grant_type=password" | python3 -c "import sys, json; print(json.load(sys.stdin).get('access_token', ''))") | |
| fi | |
| if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then | |
| echo "Failed to get Keycloak admin token after retry" | |
| exit 1 | |
| fi | |
| echo "Creating tasks realm..." | |
| REALM_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST http://localhost:8080/admin/realms \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "realm": "tasks", | |
| "enabled": true | |
| }') | |
| HTTP_CODE=$(echo "$REALM_RESPONSE" | tail -n1) | |
| if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "409" ]; then | |
| echo "Realm created or already exists (HTTP $HTTP_CODE)" | |
| else | |
| echo "Failed to create realm (HTTP $HTTP_CODE)" | |
| echo "$REALM_RESPONSE" | |
| fi | |
| echo "Creating tasks-web client..." | |
| CLIENT_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST http://localhost:8080/admin/realms/tasks/clients \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "clientId": "tasks-web", | |
| "enabled": true, | |
| "publicClient": false, | |
| "secret": "tasks-secret", | |
| "directAccessGrantsEnabled": true, | |
| "standardFlowEnabled": true, | |
| "redirectUris": ["http://localhost:3000/*", "http://localhost:8999/callback"] | |
| }') | |
| CLIENT_HTTP_CODE=$(echo "$CLIENT_RESPONSE" | tail -n1) | |
| if [ "$CLIENT_HTTP_CODE" = "201" ] || [ "$CLIENT_HTTP_CODE" = "409" ]; then | |
| echo "Client created or already exists (HTTP $CLIENT_HTTP_CODE)" | |
| else | |
| echo "Failed to create client (HTTP $CLIENT_HTTP_CODE)" | |
| echo "$CLIENT_RESPONSE" | |
| fi | |
| echo "Creating test user..." | |
| USER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST http://localhost:8080/admin/realms/tasks/users \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "username": "test", | |
| "enabled": true, | |
| "credentials": [{ | |
| "type": "password", | |
| "value": "test", | |
| "temporary": false | |
| }] | |
| }') | |
| USER_HTTP_CODE=$(echo "$USER_RESPONSE" | tail -n1) | |
| if [ "$USER_HTTP_CODE" = "201" ] || [ "$USER_HTTP_CODE" = "409" ]; then | |
| echo "User created or already exists (HTTP $USER_HTTP_CODE)" | |
| else | |
| echo "Failed to create user (HTTP $USER_HTTP_CODE)" | |
| echo "$USER_RESPONSE" | |
| fi | |
| echo "Keycloak setup complete" | |
| - name: Start backend server | |
| working-directory: backend | |
| env: | |
| DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres | |
| REDIS_URL: redis://localhost:6379 | |
| ENV: test | |
| ISSUER_URI: http://localhost:8080/realms/tasks | |
| CLIENT_ID: tasks-backend | |
| CLIENT_SECRET: tasks-secret | |
| run: | | |
| nohup uvicorn main:app --host 0.0.0.0 --port 8000 > /tmp/backend_simulator.log 2>&1 & | |
| echo $! > /tmp/backend_simulator.pid | |
| sleep 5 | |
| - name: Wait for backend | |
| run: | | |
| timeout 60 bash -c 'until curl -f -s http://localhost:8000/health; do sleep 2; done' | |
| - name: Run simulator test | |
| working-directory: simulator | |
| env: | |
| DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres | |
| REDIS_URL: redis://localhost:6379 | |
| GRAPHQL_URL: http://localhost:8000/graphql | |
| KEYCLOAK_URL: http://localhost:8080 | |
| USE_DIRECT_GRANT: "true" | |
| USERNAME: test | |
| PASSWORD: test | |
| REALM: tasks | |
| CLIENT_ID: tasks-web | |
| CLIENT_SECRET: tasks-secret | |
| run: | | |
| timeout 30 python3 main.py 2>&1 | tee /tmp/simulator_test.log | |
| if grep -q "ERROR\|Error" /tmp/simulator_test.log; then | |
| echo "Simulator test failed with errors" | |
| exit 1 | |
| fi | |
| - name: Upload simulator logs | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: simulator-logs | |
| path: | | |
| /tmp/backend_simulator.log | |
| /tmp/simulator_test.log | |
| retention-days: 7 | |
| frontend-lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| cache-dependency-path: web/package-lock.json | |
| - name: Install dependencies | |
| working-directory: web | |
| run: npm ci | |
| - name: Run linter | |
| working-directory: web | |
| run: npm run lint | |
| backend-tests: | |
| needs: [backend-lint] | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| python-version: ["3.11", "3.12", "3.13"] | |
| services: | |
| postgres: | |
| image: postgres:15 | |
| env: | |
| POSTGRES_USER: test | |
| POSTGRES_PASSWORD: test | |
| POSTGRES_DB: test | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| redis: | |
| image: redis:7-alpine | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Cache pip packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-${{ hashFiles('backend/requirements.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip- | |
| - name: Install dependencies | |
| working-directory: backend | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Run unit tests | |
| working-directory: backend | |
| run: | | |
| pytest tests/unit -v --cov=api --cov=database --cov-report=xml --cov-report=term | |
| - name: Run integration tests | |
| working-directory: backend | |
| run: | | |
| pytest tests/integration -v | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| file: ./backend/coverage.xml | |
| flags: backend | |
| name: backend-coverage | |
| frontend-tests: | |
| needs: [frontend-lint] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| cache-dependency-path: web/package-lock.json | |
| - name: Install dependencies | |
| working-directory: web | |
| run: npm ci | |
| - name: Type check | |
| working-directory: web | |
| run: npx tsc --noEmit | |
| e2e-tests: | |
| needs: [backend-tests, frontend-tests] | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: postgres:15 | |
| env: | |
| POSTGRES_USER: test | |
| POSTGRES_PASSWORD: test | |
| POSTGRES_DB: test | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 5432:5432 | |
| redis: | |
| image: redis:7-alpine | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| ports: | |
| - 6379:6379 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| cache-dependency-path: web/package-lock.json | |
| - name: Install backend dependencies | |
| working-directory: backend | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Install frontend dependencies | |
| working-directory: web | |
| run: npm ci | |
| - name: Install E2E test dependencies | |
| working-directory: tests | |
| run: npm ci | |
| - name: Install Playwright browsers | |
| working-directory: tests | |
| run: npx playwright install --with-deps chromium | |
| continue-on-error: true | |
| - name: Run database migrations | |
| working-directory: backend | |
| run: | | |
| alembic upgrade head | |
| env: | |
| DATABASE_URL: postgresql+asyncpg://test:test@localhost:5432/test | |
| - name: Start backend server | |
| working-directory: backend | |
| run: | | |
| uvicorn main:app --host 0.0.0.0 --port 8000 > /tmp/backend.log 2>&1 & | |
| BACKEND_PID=$! | |
| echo $BACKEND_PID > /tmp/backend.pid | |
| echo "Backend started with PID: $BACKEND_PID" | |
| sleep 3 | |
| env: | |
| DATABASE_URL: postgresql+asyncpg://test:test@localhost:5432/test | |
| REDIS_URL: redis://localhost:6379 | |
| ISSUER_URI: http://localhost:8080/realms/tasks | |
| PUBLIC_ISSUER_URI: http://localhost:8080/realms/tasks | |
| CLIENT_ID: tasks-backend | |
| CLIENT_SECRET: tasks-secret | |
| ENV: test | |
| INFLUXDB_URL: http://localhost:8086 | |
| INFLUXDB_TOKEN: test-token | |
| INFLUXDB_ORG: test | |
| INFLUXDB_BUCKET: test | |
| - name: Build frontend | |
| working-directory: web | |
| run: npm run build | |
| env: | |
| NEXT_PUBLIC_API_URL: http://localhost:8000/graphql | |
| - name: Start frontend server | |
| working-directory: web | |
| run: | | |
| npm start > /tmp/frontend.log 2>&1 & | |
| FRONTEND_PID=$! | |
| echo $FRONTEND_PID > /tmp/frontend.pid | |
| echo "Frontend started with PID: $FRONTEND_PID" | |
| sleep 3 | |
| env: | |
| NEXT_PUBLIC_API_URL: http://localhost:8000/graphql | |
| - name: Wait for backend | |
| run: | | |
| echo "Waiting for backend to start..." | |
| sleep 10 | |
| for i in {1..60}; do | |
| if curl -f -s http://localhost:8000/health > /dev/null 2>&1; then | |
| echo "Backend is ready!" | |
| exit 0 | |
| fi | |
| if [ $i -le 10 ]; then | |
| echo "Attempt $i/60: Backend not ready yet..." | |
| fi | |
| sleep 2 | |
| done | |
| echo "Backend failed to start after 120 seconds" | |
| echo "=== Backend Log ===" | |
| cat /tmp/backend.log || echo "No backend log found" | |
| echo "=== Checking if process is running ===" | |
| ps aux | grep uvicorn || echo "No uvicorn process found" | |
| exit 1 | |
| - name: Wait for frontend | |
| run: | | |
| echo "Waiting for frontend to start..." | |
| sleep 5 | |
| for i in {1..60}; do | |
| if curl -f -s http://localhost:3000 > /dev/null 2>&1; then | |
| echo "Frontend is ready!" | |
| exit 0 | |
| fi | |
| echo "Attempt $i/60: Frontend not ready yet..." | |
| sleep 2 | |
| done | |
| echo "Frontend failed to start after 120 seconds" | |
| echo "=== Frontend Log ===" | |
| cat /tmp/frontend.log || echo "No frontend log found" | |
| exit 1 | |
| - name: Verify servers are running | |
| run: | | |
| echo "=== Verifying servers ===" | |
| if curl -f -s http://localhost:8000/health > /dev/null 2>&1; then | |
| echo "✓ Backend is running" | |
| else | |
| echo "✗ Backend is not running" | |
| echo "Backend log:" | |
| tail -20 /tmp/backend.log || echo "No backend log" | |
| exit 1 | |
| fi | |
| if curl -f -s http://localhost:3000 > /dev/null 2>&1; then | |
| echo "✓ Frontend is running" | |
| else | |
| echo "✗ Frontend is not running" | |
| echo "Frontend log:" | |
| tail -20 /tmp/frontend.log || echo "No frontend log" | |
| exit 1 | |
| fi | |
| echo "=== Server processes ===" | |
| ps aux | grep -E "(uvicorn|node)" | grep -v grep || echo "No server processes found" | |
| - name: Run E2E tests | |
| working-directory: tests | |
| env: | |
| E2E_BASE_URL: http://localhost:3000 | |
| CI: true | |
| run: | | |
| echo "E2E_BASE_URL is set to: $E2E_BASE_URL" | |
| echo "Testing connection to frontend..." | |
| curl -f -s http://localhost:3000 > /dev/null && echo "Frontend is accessible" || echo "Frontend is not accessible" | |
| npx playwright test | |
| - name: Upload Playwright report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report | |
| path: tests/playwright-report/ | |
| retention-days: 30 | |
| - name: Upload server logs | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: server-logs | |
| path: | | |
| /tmp/backend.log | |
| /tmp/frontend.log | |
| retention-days: 7 | |
| build: | |
| needs: [backend-tests, frontend-tests, e2e-tests, simulator-lint, simulator-tests] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| cache-dependency-path: web/package-lock.json | |
| - name: Install backend dependencies | |
| working-directory: backend | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Install frontend dependencies | |
| working-directory: web | |
| run: npm ci | |
| - name: Build frontend | |
| working-directory: web | |
| run: npm run build | |
| - name: Upload build artifacts | |
| if: success() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: frontend-build | |
| path: web/build | |
| retention-days: 7 |