diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..3fad7e0 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,50 @@ +name: CRMS CD Pipeline + +on: + push: + branches: [ main ] + +jobs: + + push-to-registry: + name: Push images to GHCR + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push backend + uses: docker/build-push-action@v5 + with: + context: ./backend + push: true + tags: | + ghcr.io/crms-devops/crms-backend:latest + ghcr.io/crms-devops/crms-backend:${{ github.sha }} + + - name: Build and push frontend + uses: docker/build-push-action@v5 + with: + context: ./frontend + push: true + tags: | + ghcr.io/crms-devops/crms-frontend:latest + ghcr.io/crms-devops/crms-frontend:${{ github.sha }} + + - name: Summary + run: | + echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY + echo "Backend: ghcr.io/crms-devops/crms-backend:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "Frontend: ghcr.io/crms-devops/crms-frontend:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..31ed75c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,130 @@ +name: CRMS CI Pipeline + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ develop, main ] + +jobs: + + backend-test: + name: Backend — pytest + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_DB: crms_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: testpassword + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Cache pip packages + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('backend/requirements.txt') }} + + - name: Install dependencies + run: | + cd backend + pip install -r requirements.txt + pip install pytest pytest-asyncio httpx + + - name: Run pytest + env: + DATABASE_URL: postgresql://postgres:testpassword@localhost:5432/crms_test + SECRET_KEY: test-secret-key + ALGORITHM: HS256 + ACCESS_TOKEN_EXPIRE_MINUTES: 60 + run: | + cd backend + pytest tests/ -v --tb=short + + frontend-lint: + name: Frontend — ESLint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node 20 + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: | + cd frontend + npm ci + + - name: Run ESLint + run: | + cd frontend + npm run lint + + docker-build-scan: + name: Docker — build + Trivy scan + runs-on: ubuntu-latest + needs: [ backend-test, frontend-lint ] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build backend image + uses: docker/build-push-action@v5 + with: + context: ./backend + push: false + tags: crms-backend:${{ github.sha }} + load: true + + - name: Build frontend image + uses: docker/build-push-action@v5 + with: + context: ./frontend + push: false + tags: crms-frontend:${{ github.sha }} + load: true + + - name: Trivy scan — backend + uses: aquasecurity/trivy-action@master + with: + image-ref: crms-backend:${{ github.sha }} + format: table + exit-code: 1 + severity: CRITICAL + ignore-unfixed: true + + - name: Trivy scan — frontend + uses: aquasecurity/trivy-action@master + with: + image-ref: crms-frontend:${{ github.sha }} + format: table + exit-code: 1 + severity: CRITICAL + ignore-unfixed: true \ No newline at end of file diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..bfab6ab --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,7 @@ +import pytest +from fastapi.testclient import TestClient +from app.main import app + +@pytest.fixture +def client(): + return TestClient(app) \ No newline at end of file diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py new file mode 100644 index 0000000..4578ccd --- /dev/null +++ b/backend/tests/test_health.py @@ -0,0 +1,15 @@ +def test_health(client): + response = client.get("/health") + assert response.status_code == 200 + assert response.json()["status"] == "ok" + +def test_login_missing_fields(client): + response = client.post("/auth/student/login", json={}) + assert response.status_code == 422 + +def test_login_invalid_credentials(client): + response = client.post("/auth/student/login", json={ + "register_number": "000000000000", + "date_of_birth": "2000-01-01" + }) + assert response.status_code == 401 \ No newline at end of file