diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index e471371..1e6326f 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -15,11 +15,18 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Build & push image + - name: Build image + run: docker build -t "${IMAGE}:latest" -t "${IMAGE}:${GITHUB_SHA::12}" . + + - name: Tests (pytest dans l'image, BDD simulée) + run: | + docker run --rm "${IMAGE}:${GITHUB_SHA::12}" \ + sh -c 'pip install -q pytest httpx && pytest -q' + + - name: Push image run: | set -e echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.nfteam.ovh -u "${{ secrets.REGISTRY_USER }}" --password-stdin - docker build -t "${IMAGE}:latest" -t "${IMAGE}:${GITHUB_SHA::12}" . docker push --all-tags "${IMAGE}" echo "pushed ${IMAGE}" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..3b54a3f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,53 @@ +"""Fixtures de test : client FastAPI + curseur SQL simulé (aucune vraie BDD).""" +import os +from contextlib import contextmanager + +os.environ.setdefault("JWT_SECRET", "test-secret") + +import pytest +from fastapi.testclient import TestClient + +import main +from auth import create_access_token + + +class FakeCursor: + """Curseur pyodbc simulé : on règle description / rows / one par test.""" + def __init__(self): + self.description = [] + self._rows = [] + self._one = None + self.executed = [] + + def execute(self, query, *args): + self.executed.append((query, args)) + return self + + def fetchall(self): + return self._rows + + def fetchone(self): + return self._one + + +@pytest.fixture +def cur(monkeypatch): + c = FakeCursor() + + @contextmanager + def fake_get_cursor(): + yield c + + monkeypatch.setattr(main, "get_cursor", fake_get_cursor) + return c + + +@pytest.fixture +def client(): + return TestClient(main.app) + + +@pytest.fixture +def auth_headers(): + token = create_access_token({"sub": "admin", "role": "Admin", "uid": 1}) + return {"Authorization": f"Bearer {token}"} diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py new file mode 100644 index 0000000..78af6ee --- /dev/null +++ b/tests/test_endpoints.py @@ -0,0 +1,54 @@ +"""Tests d'endpoints (curseur SQL simulé via la fixture `cur`).""" +from auth import hash_password, create_access_token + + +def test_health_ok(client, cur): + cur._one = (1,) + r = client.get("/health") + assert r.status_code == 200 + assert r.json()["api"] == "ok" + + +def test_categories_requires_auth(client): + # sans token -> 401 + assert client.get("/categories").status_code == 401 + + +def test_services_returns_list(client, cur, auth_headers): + cur.description = [("id_service",), ("nom_service",)] + cur._rows = [(1, "Contrat"), (2, "Fournisseur")] + r = client.get("/services", headers=auth_headers) + assert r.status_code == 200 + assert {"id_service": 1, "nom_service": "Contrat"} in r.json() + + +def test_auth_login_success(client, cur): + cur._one = (1, "admin", hash_password("Admin2026!"), "Admin", 1) + r = client.post("/auth/login", data={"username": "admin", "password": "Admin2026!"}) + assert r.status_code == 200 + body = r.json() + assert body["token_type"] == "bearer" + assert body["user"]["role"] == "Admin" + + +def test_auth_login_wrong_password(client, cur): + cur._one = (1, "admin", hash_password("Admin2026!"), "Admin", 1) + r = client.post("/auth/login", data={"username": "admin", "password": "WRONG"}) + assert r.status_code == 401 + + +def test_auth_me(client, auth_headers): + r = client.get("/auth/me", headers=auth_headers) + assert r.status_code == 200 + assert r.json()["username"] == "admin" + + +def test_monitoring_unknown_returns_404(client, cur, auth_headers): + r = client.get("/monitorings/999/details", headers=auth_headers) + assert r.status_code == 404 + + +def test_admin_forbidden_for_consultant(client): + token = create_access_token({"sub": "bob", "role": "Consultant", "uid": 2}) + r = client.get("/admin/users", headers={"Authorization": f"Bearer {token}"}) + assert r.status_code == 403