ai-talent-finder-backend / tests /test_admin_api.py
ilyass yani
Deploiement backend dans HF Spaces
9df97a2
Raw
History Blame
10.4 kB
#!/usr/bin/env python3
"""
Unit tests for the admin API endpoints.
Follows the same pattern as test_api_unit.py.
NOTE: The app registers routers in on_startup(), so we must use TestClient
as a context manager to trigger startup before making requests.
"""
import pytest
from unittest.mock import patch, MagicMock
from fastapi.testclient import TestClient
try:
from app.main import app
except Exception:
with patch.dict("sys.modules", {
"ai_module": MagicMock(),
"ai_module.nlp": MagicMock(),
"ai_module.nlp.cv_cleaner": MagicMock(),
"ai_module.nlp.skill_extractor": MagicMock(),
"ai_module.nlp.profile_generator": MagicMock(),
}):
from app.main import app
from app.models.models import User, UserRole
from app.core.security import get_password_hash
# ---------------------------------------------------------------------------
# Client fixture — triggers on_startup (which registers all routers)
# ---------------------------------------------------------------------------
@pytest.fixture(scope="module")
def client():
with TestClient(app, raise_server_exceptions=False) as c:
yield c
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _register(client, email: str, role: str = "recruiter") -> dict:
res = client.post(
"/api/auth/register",
json={"email": email, "password": "Password123!", "full_name": "Test User", "role": role},
)
return res.json()
def _login_token(client, email: str) -> str:
res = client.post(
"/api/auth/login",
json={"email": email, "password": "Password123!"},
)
assert res.status_code == 200, f"Login failed: {res.json()}"
return res.json()["access_token"]
def _auth(token: str) -> dict:
return {"Authorization": f"Bearer {token}"}
@pytest.fixture(scope="module")
def admin_token(client):
"""Create an admin user directly in the DB and obtain a JWT."""
from app.core.database import SessionLocal
db = SessionLocal()
try:
existing = db.query(User).filter(User.email == "admin_test@example.io").first()
if not existing:
admin = User(
email="admin_test@example.io",
hashed_password=get_password_hash("Admin1234!"),
full_name="Admin Test",
role=UserRole.admin,
is_active=True,
)
db.add(admin)
db.commit()
finally:
db.close()
res = client.post(
"/api/auth/login",
json={"email": "admin_test@example.io", "password": "Admin1234!"},
)
assert res.status_code == 200, f"Admin login failed: {res.json()}"
return res.json()["access_token"]
# ---------------------------------------------------------------------------
# Tests: authorization guard
# ---------------------------------------------------------------------------
class TestAdminAuthGuard:
"""Non-admin users must get 403 on all /api/admin/* routes."""
def test_recruiter_cannot_access_admin_users(self, client):
_register(client, "recruiter_guard@example.io", role="recruiter")
token = _login_token(client, "recruiter_guard@example.io")
res = client.get("/api/admin/users/", headers=_auth(token))
assert res.status_code == 403
def test_candidate_cannot_access_admin_users(self, client):
_register(client, "candidate_guard@example.io", role="candidate")
token = _login_token(client, "candidate_guard@example.io")
res = client.get("/api/admin/users/", headers=_auth(token))
assert res.status_code == 403
def test_unauthenticated_cannot_access_admin(self, client):
res = client.get("/api/admin/users/")
assert res.status_code == 401
def test_recruiter_cannot_access_admin_stats(self, client):
_register(client, "rec_stats@example.io", role="recruiter")
token = _login_token(client, "rec_stats@example.io")
res = client.get("/api/admin/stats/", headers=_auth(token))
assert res.status_code == 403
# ---------------------------------------------------------------------------
# Tests: admin user endpoints
# ---------------------------------------------------------------------------
class TestAdminUsers:
def test_list_users_returns_paginated(self, client, admin_token):
res = client.get("/api/admin/users/", headers=_auth(admin_token))
assert res.status_code == 200
data = res.json()
assert "total" in data
assert "items" in data
assert isinstance(data["items"], list)
def test_list_users_filter_by_role(self, client, admin_token):
_register(client, "filter_candidate@example.io", role="candidate")
res = client.get("/api/admin/users/?role=candidate", headers=_auth(admin_token))
assert res.status_code == 200
for item in res.json()["items"]:
assert item["role"] == "candidate"
def test_set_user_status_deactivate(self, client, admin_token):
_register(client, "to_deactivate@example.io", role="recruiter")
users = client.get(
"/api/admin/users/?role=recruiter", headers=_auth(admin_token)
).json()["items"]
target = next((u for u in users if u["email"] == "to_deactivate@example.io"), None)
assert target is not None
res = client.patch(
f"/api/admin/users/{target['id']}/status/",
json={"is_active": False},
headers=_auth(admin_token),
)
assert res.status_code == 200
assert res.json()["is_active"] is False
def test_set_user_status_not_found(self, client, admin_token):
res = client.patch(
"/api/admin/users/999999/status/",
json={"is_active": True},
headers=_auth(admin_token),
)
assert res.status_code == 404
def test_delete_user(self, client, admin_token):
_register(client, "to_delete@example.io", role="candidate")
users = client.get(
"/api/admin/users/?role=candidate", headers=_auth(admin_token)
).json()["items"]
target = next((u for u in users if u["email"] == "to_delete@example.io"), None)
assert target is not None
res = client.delete(f"/api/admin/users/{target['id']}/", headers=_auth(admin_token))
assert res.status_code == 204
def test_delete_user_not_found(self, client, admin_token):
res = client.delete("/api/admin/users/999999/", headers=_auth(admin_token))
assert res.status_code == 404
# ---------------------------------------------------------------------------
# Tests: admin job endpoints
# ---------------------------------------------------------------------------
class TestAdminJobs:
def _create_job(self, client, recruiter_token: str) -> dict:
res = client.post(
"/api/jobs/",
json={"title": "Admin Test Job", "description": "Test", "criteria_skills": []},
headers=_auth(recruiter_token),
)
assert res.status_code in (200, 201), f"Job creation failed: {res.json()}"
return res.json()
def test_list_jobs_returns_paginated(self, client, admin_token):
res = client.get("/api/admin/jobs/", headers=_auth(admin_token))
assert res.status_code == 200
data = res.json()
assert "total" in data
assert "items" in data
def test_moderate_job_approve(self, client, admin_token):
_register(client, "rec_for_jobs@example.io", role="recruiter")
rec_token = _login_token(client, "rec_for_jobs@example.io")
job = self._create_job(client, rec_token)
res = client.patch(
f"/api/admin/jobs/{job['id']}/moderate/",
json={"moderation_status": "approved"},
headers=_auth(admin_token),
)
assert res.status_code == 200
assert res.json()["moderation_status"] == "approved"
def test_moderate_job_reject(self, client, admin_token):
_register(client, "rec_for_reject@example.io", role="recruiter")
rec_token = _login_token(client, "rec_for_reject@example.io")
job = self._create_job(client, rec_token)
res = client.patch(
f"/api/admin/jobs/{job['id']}/moderate/",
json={"moderation_status": "rejected"},
headers=_auth(admin_token),
)
assert res.status_code == 200
assert res.json()["moderation_status"] == "rejected"
def test_moderate_job_not_found(self, client, admin_token):
res = client.patch(
"/api/admin/jobs/999999/moderate/",
json={"moderation_status": "approved"},
headers=_auth(admin_token),
)
assert res.status_code == 404
def test_delete_job(self, client, admin_token):
_register(client, "rec_for_delete@example.io", role="recruiter")
rec_token = _login_token(client, "rec_for_delete@example.io")
job = self._create_job(client, rec_token)
res = client.delete(f"/api/admin/jobs/{job['id']}/", headers=_auth(admin_token))
assert res.status_code == 204
def test_delete_job_not_found(self, client, admin_token):
res = client.delete("/api/admin/jobs/999999/", headers=_auth(admin_token))
assert res.status_code == 404
# ---------------------------------------------------------------------------
# Tests: admin stats endpoint
# ---------------------------------------------------------------------------
class TestAdminStats:
def test_stats_returns_expected_fields(self, client, admin_token):
res = client.get("/api/admin/stats/", headers=_auth(admin_token))
assert res.status_code == 200
data = res.json()
for field in ["total_candidates", "total_recruiters", "total_active_jobs", "total_matchings"]:
assert field in data
def test_stats_values_are_non_negative(self, client, admin_token):
res = client.get("/api/admin/stats/", headers=_auth(admin_token))
assert res.status_code == 200
data = res.json()
for field in ["total_candidates", "total_recruiters", "total_active_jobs", "total_matchings"]:
assert data[field] >= 0