Skip to content

Refactoring and testing #39

Refactoring and testing

Refactoring and testing #39

Workflow file for this run

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
steps:
- uses: actions/checkout@v4
- name: Start Keycloak
run: |
docker run -d --name keycloak \
-p 8080:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
-e KC_HEALTH_ENABLED=true \
-e KC_METRICS_ENABLED=true \
-e KC_DB=dev-file \
quay.io/keycloak/keycloak:25.0 \
start-dev
- name: Wait for Keycloak to be ready
run: |
timeout 60s bash -c 'until curl -f http://localhost:8080/health/ready; do sleep 5; done'
- 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