603 lines
23 KiB
Python
603 lines
23 KiB
Python
"""
|
|
tests/test_regwatch_complet.py
|
|
==========================================================
|
|
Tests complets RegWatch — conformément au guide Nexa DBI
|
|
Mis à jour pour correspondre au vrai code main.py + auth.py
|
|
Couvre :
|
|
- Tests unitaires (scraper CIR)
|
|
- Tests d'intégration (endpoints API)
|
|
- Tests de sécurité (injection SQL, JWT, XSS)
|
|
- Conformité RGPD (mdp haché, données non exposées)
|
|
- Tests endpoints admin
|
|
==========================================================
|
|
"""
|
|
|
|
import pytest
|
|
import sys
|
|
import os
|
|
import time
|
|
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
# ── Import app FastAPI ────────────────────────────────────────
|
|
try:
|
|
from fastapi.testclient import TestClient
|
|
from api.main import app
|
|
client = TestClient(app)
|
|
APP_OK = True
|
|
except Exception as e:
|
|
APP_OK = False
|
|
|
|
def skip_no_app():
|
|
if not APP_OK:
|
|
pytest.skip("App / BDD non disponible")
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# PARTIE 1 — TESTS UNITAIRES (scraper CIR)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
class TestUnitairesExtractPagesText:
|
|
"""Tests unitaires de extract_pages_text() — cir_scraper.py"""
|
|
|
|
def test_bytes_vide_retourne_chaine_vide(self):
|
|
from scrapers.cir_scraper import extract_pages_text
|
|
assert extract_pages_text(b"") == ""
|
|
|
|
def test_pdf_invalide_retourne_chaine_vide(self):
|
|
from scrapers.cir_scraper import extract_pages_text
|
|
assert extract_pages_text(b"ceci n est pas un pdf") == ""
|
|
|
|
def test_retour_toujours_une_str(self):
|
|
from scrapers.cir_scraper import extract_pages_text
|
|
result = extract_pages_text(b"")
|
|
assert isinstance(result, str)
|
|
|
|
def test_limite_5000_caracteres(self):
|
|
long_text = "A" * 10000
|
|
assert len(long_text[:5000]) == 5000
|
|
|
|
|
|
class TestUnitairesFallbackOllama:
|
|
"""Tests unitaires du fallback Ollama"""
|
|
|
|
def test_fallback_utilise_nom_fichier(self):
|
|
pdf_url = "https://cir-safety.org/sites/files/Retinol_2024.pdf"
|
|
filename = pdf_url.split("/")[-1].replace("_", " ").replace(".pdf", "")
|
|
assert filename == "Retinol 2024"
|
|
|
|
def test_fallback_retourne_4_cles(self):
|
|
fallback = {
|
|
"title": "Mon Document",
|
|
"ingredient": None,
|
|
"document_type": "document",
|
|
"date": None,
|
|
}
|
|
assert all(k in fallback for k in ["title", "ingredient", "document_type", "date"])
|
|
assert fallback["ingredient"] is None
|
|
|
|
def test_nettoyage_ingredient_null_string(self):
|
|
for val in ["null", "none", "n/a", "", "NULL", "None"]:
|
|
result = None if str(val).strip().lower() in ("null", "none", "n/a", "") else val
|
|
assert result is None
|
|
|
|
def test_titre_tronque_200_chars(self):
|
|
long = "A" * 300
|
|
assert len(str(long).strip()[:200]) == 200
|
|
|
|
|
|
class TestUnitairesTemplatesURL:
|
|
"""Tests unitaires des templates d'URL CIR"""
|
|
|
|
def test_tous_les_templates_contiennent_num(self):
|
|
from scrapers.cir_scraper import url_templates
|
|
for t in url_templates:
|
|
assert "{num}" in t
|
|
|
|
def test_tous_les_templates_sont_https(self):
|
|
from scrapers.cir_scraper import url_templates
|
|
for t in url_templates:
|
|
assert t.startswith("https://")
|
|
|
|
def test_format_url_remplace_num(self):
|
|
from scrapers.cir_scraper import url_templates
|
|
url = url_templates[0].format(num=150)
|
|
assert "150" in url
|
|
assert "{num}" not in url
|
|
|
|
def test_meeting_numbers_couvre_115_a_174(self):
|
|
from scrapers.cir_scraper import MEETING_NUMBERS
|
|
nums = list(MEETING_NUMBERS)
|
|
assert nums[0] == 115
|
|
assert nums[-1] == 174
|
|
assert len(nums) == 60
|
|
|
|
def test_pdf_limit_est_100(self):
|
|
from scrapers.cir_scraper import PDF_LIMIT
|
|
assert PDF_LIMIT == 100
|
|
|
|
|
|
class TestUnitairesFiltragePDF:
|
|
"""Tests unitaires du filtrage des liens PDF"""
|
|
|
|
def test_skip_keywords_contient_les_3_valeurs(self):
|
|
from scrapers.cir_scraper import SKIP_KEYWORDS
|
|
assert "Agenda" in SKIP_KEYWORDS
|
|
assert "Minutes" in SKIP_KEYWORDS
|
|
assert "Status Report" in SKIP_KEYWORDS
|
|
|
|
def test_regex_pdf_detecte_extension_pdf(self):
|
|
from scrapers.cir_scraper import PDF_RE
|
|
assert PDF_RE.search("document.pdf")
|
|
assert PDF_RE.search("document.PDF")
|
|
assert PDF_RE.search("rapport.Pdf")
|
|
|
|
def test_regex_pdf_ne_detecte_pas_autres(self):
|
|
from scrapers.cir_scraper import PDF_RE
|
|
assert not PDF_RE.search("document.docx")
|
|
assert not PDF_RE.search("image.png")
|
|
|
|
def test_agenda_est_filtre(self):
|
|
from scrapers.cir_scraper import SKIP_KEYWORDS
|
|
context = "116th Expert Panel Meeting Agenda"
|
|
assert any(kw in context for kw in SKIP_KEYWORDS)
|
|
|
|
def test_final_report_non_filtre(self):
|
|
from scrapers.cir_scraper import SKIP_KEYWORDS
|
|
context = "Final Report on the Safety Assessment of Retinol"
|
|
assert not any(kw in context for kw in SKIP_KEYWORDS)
|
|
|
|
|
|
class TestUnitairesDeduplication:
|
|
"""Tests unitaires de la logique de déduplication"""
|
|
|
|
def test_nouvelle_url_acceptee(self):
|
|
existing = {"https://cir.org/doc1.pdf"}
|
|
assert "https://cir.org/doc3.pdf" not in existing
|
|
|
|
def test_url_existante_rejetee(self):
|
|
existing = {"https://cir.org/doc1.pdf"}
|
|
assert "https://cir.org/doc1.pdf" in existing
|
|
|
|
def test_structure_doc_6_champs(self):
|
|
doc = {
|
|
"title": "Final Report on Retinol",
|
|
"ingredient": "Retinol",
|
|
"source": "CIR",
|
|
"document_type": "final report",
|
|
"meeting_date": "2024",
|
|
"pdf_url": "https://cir.org/retinol_2024.pdf",
|
|
}
|
|
for champ in ["title", "ingredient", "source", "document_type", "meeting_date", "pdf_url"]:
|
|
assert champ in doc
|
|
|
|
def test_source_est_cir(self):
|
|
assert "CIR" == "CIR"
|
|
|
|
|
|
class TestUnitairesHeadersHTTP:
|
|
"""Tests unitaires des headers HTTP anti-blocage"""
|
|
|
|
def test_user_agent_simule_chrome(self):
|
|
from scrapers.cir_scraper import headers
|
|
assert "User-Agent" in headers
|
|
assert "Mozilla" in headers["User-Agent"]
|
|
assert "Chrome" in headers["User-Agent"]
|
|
|
|
def test_session_utilise_les_headers(self):
|
|
from scrapers.cir_scraper import session
|
|
assert "User-Agent" in session.headers
|
|
assert "Mozilla" in session.headers["User-Agent"]
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# PARTIE 2 — TESTS D'INTÉGRATION (endpoints FastAPI)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
class TestIntegrationRacine:
|
|
"""Tests de GET /"""
|
|
|
|
def test_racine_200(self):
|
|
skip_no_app()
|
|
assert client.get("/").status_code == 200
|
|
|
|
def test_racine_retourne_message_running(self):
|
|
skip_no_app()
|
|
data = client.get("/").json()
|
|
assert "message" in data
|
|
assert "running" in data["message"].lower() or "RegWatch" in data["message"]
|
|
|
|
def test_racine_retourne_json(self):
|
|
skip_no_app()
|
|
r = client.get("/")
|
|
assert r.headers["content-type"].startswith("application/json")
|
|
|
|
|
|
class TestIntegrationSignup:
|
|
"""Tests de POST /signup"""
|
|
|
|
def test_email_invalide_422(self):
|
|
skip_no_app()
|
|
r = client.post("/signup", json={"email": "pasunemail", "password": "test", "full_name": "T"})
|
|
assert r.status_code == 422
|
|
|
|
def test_champs_manquants_422(self):
|
|
skip_no_app()
|
|
r = client.post("/signup", json={"email": "test@test.com"})
|
|
assert r.status_code == 422
|
|
|
|
def test_email_deja_existant_400(self):
|
|
skip_no_app()
|
|
email = f"doublon_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
r = client.post("/signup", json={"email": email, "password": "autre", "full_name": "T"})
|
|
assert r.status_code == 400
|
|
|
|
def test_signup_reussi_retourne_id_email_role(self):
|
|
"""UserResponse retourne maintenant id, email, full_name ET role"""
|
|
skip_no_app()
|
|
email = f"ok_{int(time.time())}@regwatch.fr"
|
|
r = client.post("/signup", json={"email": email, "password": "test123", "full_name": "OK"})
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert "id" in data
|
|
assert data["email"] == email
|
|
assert "role" in data
|
|
assert data["role"] == "user" # role par défaut
|
|
|
|
|
|
class TestIntegrationLogin:
|
|
"""Tests de POST /login"""
|
|
|
|
def test_email_inexistant_401(self):
|
|
skip_no_app()
|
|
r = client.post("/login", json={"email": "xyz_inconnu@regwatch.fr", "password": "test"})
|
|
assert r.status_code == 401
|
|
assert "not found" in r.json()["detail"].lower()
|
|
|
|
def test_mauvais_mot_de_passe_401(self):
|
|
skip_no_app()
|
|
email = f"mdp_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "bonmdp", "full_name": "T"})
|
|
r = client.post("/login", json={"email": email, "password": "mauvaismdp"})
|
|
assert r.status_code == 401
|
|
|
|
def test_login_reussi_retourne_token_bearer(self):
|
|
skip_no_app()
|
|
email = f"jwt_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
r = client.post("/login", json={"email": email, "password": "test123"})
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert "access_token" in data
|
|
assert data["token_type"] == "bearer"
|
|
assert len(data["access_token"]) > 10
|
|
|
|
def test_corps_vide_422(self):
|
|
skip_no_app()
|
|
assert client.post("/login", json={}).status_code == 422
|
|
|
|
|
|
class TestIntegrationMe:
|
|
"""Tests de GET /me?token=..."""
|
|
|
|
def test_sans_token_422(self):
|
|
skip_no_app()
|
|
assert client.get("/me").status_code == 422
|
|
|
|
def test_token_invalide_401(self):
|
|
skip_no_app()
|
|
r = client.get("/me?token=token_completement_faux")
|
|
assert r.status_code == 401
|
|
|
|
def test_token_valide_retourne_user_avec_role(self):
|
|
"""GET /me retourne maintenant le role dans UserResponse"""
|
|
skip_no_app()
|
|
email = f"me_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
token = client.post("/login", json={"email": email, "password": "test123"}).json()["access_token"]
|
|
r = client.get(f"/me?token={token}")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert data["email"] == email
|
|
assert "role" in data
|
|
assert data["role"] == "user"
|
|
|
|
|
|
class TestIntegrationDocuments:
|
|
"""Tests de GET /documents"""
|
|
|
|
def test_sans_auth_retourne_200(self):
|
|
"""GET /documents ne requiert pas de JWT"""
|
|
skip_no_app()
|
|
assert client.get("/documents").status_code == 200
|
|
|
|
def test_retourne_liste_json(self):
|
|
skip_no_app()
|
|
assert isinstance(client.get("/documents").json(), list)
|
|
|
|
def test_structure_7_champs_par_document(self):
|
|
"""Chaque doc a id, title, ingredient, source, type, date, pdf_url"""
|
|
skip_no_app()
|
|
docs = client.get("/documents").json()
|
|
if docs:
|
|
for champ in ["id", "title", "ingredient", "source", "type", "date", "pdf_url"]:
|
|
assert champ in docs[0], f"Champ manquant : {champ}"
|
|
|
|
def test_source_cir_ou_sccs(self):
|
|
skip_no_app()
|
|
docs = client.get("/documents").json()
|
|
for doc in docs:
|
|
assert doc["source"] in ["CIR", "SCCS"]
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# PARTIE 3 — TESTS SÉCURITÉ
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
class TestSecuriteInjectionSQL:
|
|
"""Tests injection SQL — SQLAlchemy ORM protège nativement"""
|
|
|
|
def test_injection_sql_dans_email_login(self):
|
|
skip_no_app()
|
|
r = client.post("/login", json={"email": "' OR 1=1 --", "password": "test"})
|
|
assert r.status_code in [401, 422]
|
|
|
|
def test_injection_sql_dans_email_signup(self):
|
|
skip_no_app()
|
|
r = client.post("/signup", json={
|
|
"email": "'; DROP TABLE users; --",
|
|
"password": "test123",
|
|
"full_name": "Hacker"
|
|
})
|
|
assert r.status_code in [400, 422]
|
|
|
|
def test_injection_sql_guillemets_doubles(self):
|
|
skip_no_app()
|
|
r = client.post("/login", json={"email": "\" OR \"1\"=\"1", "password": "test"})
|
|
assert r.status_code != 500
|
|
|
|
|
|
class TestSecuriteJWT:
|
|
"""Tests sécurité JWT"""
|
|
|
|
def test_token_falsifie_retourne_401(self):
|
|
skip_no_app()
|
|
r = client.get("/me?token=eyJhbGciOiJIUzI1NiJ9.faux.mauvaise_signature")
|
|
assert r.status_code == 401
|
|
|
|
def test_token_vide_retourne_401_ou_422(self):
|
|
skip_no_app()
|
|
assert client.get("/me?token=").status_code in [401, 422]
|
|
|
|
def test_token_malformed_retourne_401(self):
|
|
skip_no_app()
|
|
assert client.get("/me?token=cecinestunepasjwt").status_code == 401
|
|
|
|
def test_token_valide_sur_bon_endpoint(self):
|
|
skip_no_app()
|
|
email = f"jwt_ok_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
token = client.post("/login", json={"email": email, "password": "test123"}).json()["access_token"]
|
|
assert client.get(f"/me?token={token}").status_code == 200
|
|
|
|
|
|
class TestSecuriteXSS:
|
|
"""Tests XSS"""
|
|
|
|
def test_xss_dans_full_name_stocke_sans_crash(self):
|
|
skip_no_app()
|
|
email = f"xss_{int(time.time())}@regwatch.fr"
|
|
r = client.post("/signup", json={
|
|
"email": email,
|
|
"password": "test123",
|
|
"full_name": "<script>alert('xss')</script>"
|
|
})
|
|
assert r.status_code in [200, 422]
|
|
|
|
def test_xss_dans_email_ne_plante_pas(self):
|
|
skip_no_app()
|
|
r = client.post("/login", json={
|
|
"email": "<script>alert(1)</script>@test.com",
|
|
"password": "test"
|
|
})
|
|
assert r.status_code in [401, 422]
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# PARTIE 4 — CONFORMITÉ RGPD
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
class TestRGPD:
|
|
"""Tests conformité RGPD"""
|
|
|
|
def test_mot_de_passe_absent_de_la_reponse_signup(self):
|
|
skip_no_app()
|
|
email = f"rgpd_{int(time.time())}@regwatch.fr"
|
|
r = client.post("/signup", json={
|
|
"email": email,
|
|
"password": "mon_super_secret_123",
|
|
"full_name": "RGPD Test"
|
|
})
|
|
assert r.status_code == 200
|
|
assert "mon_super_secret_123" not in r.text
|
|
assert "password" not in r.json()
|
|
|
|
def test_mot_de_passe_absent_de_la_reponse_login(self):
|
|
skip_no_app()
|
|
email = f"rgpd2_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "secret456", "full_name": "T"})
|
|
r = client.post("/login", json={"email": email, "password": "secret456"})
|
|
assert r.status_code == 200
|
|
assert "secret456" not in r.text
|
|
assert "password" not in r.json()
|
|
|
|
def test_mot_de_passe_absent_de_la_reponse_me(self):
|
|
skip_no_app()
|
|
email = f"rgpd3_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "secret789", "full_name": "T"})
|
|
token = client.post("/login", json={"email": email, "password": "secret789"}).json()["access_token"]
|
|
r = client.get(f"/me?token={token}")
|
|
assert r.status_code == 200
|
|
assert "secret789" not in r.text
|
|
assert "password" not in r.json()
|
|
|
|
def test_hachage_argon2_dans_requirements(self):
|
|
req_path = os.path.join(os.path.dirname(__file__), "..", "requirements.txt")
|
|
if os.path.exists(req_path):
|
|
with open(req_path) as f:
|
|
content = f.read()
|
|
assert "argon2" in content.lower()
|
|
else:
|
|
pytest.skip("requirements.txt non trouvé")
|
|
|
|
def test_token_jwt_structure_3_parties(self):
|
|
skip_no_app()
|
|
email = f"expire_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
token = client.post("/login", json={"email": email, "password": "test123"}).json()["access_token"]
|
|
parts = token.split(".")
|
|
assert len(parts) == 3
|
|
|
|
def test_cors_autorise_frontend(self):
|
|
skip_no_app()
|
|
r = client.get("/", headers={"Origin": "http://localhost:5173"})
|
|
assert r.status_code == 200
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# PARTIE 5 — TESTS ENDPOINTS ADMIN
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
def get_admin_token():
|
|
"""Crée un compte admin et retourne son token"""
|
|
master_email = "master_admin@regwatch.fr"
|
|
master_password = "master123"
|
|
|
|
# Essai de login master
|
|
login_r = client.post("/login", json={"email": master_email, "password": master_password})
|
|
|
|
if login_r.status_code != 200:
|
|
# Crée le master
|
|
signup_r = client.post("/signup", json={
|
|
"email": master_email,
|
|
"password": master_password,
|
|
"full_name": "Master Admin"
|
|
})
|
|
print(f"\n[DEBUG] signup master: {signup_r.status_code} {signup_r.json()}")
|
|
|
|
token_r = client.post("/login", json={"email": master_email, "password": master_password})
|
|
print(f"[DEBUG] login master: {token_r.status_code} {token_r.json()}")
|
|
master_token = token_r.json()["access_token"]
|
|
|
|
# Se promeut lui-même (aucun admin n'existe encore)
|
|
promote_r = client.post(f"/admin/make-admin?token={master_token}&target_email={master_email}")
|
|
print(f"[DEBUG] promote master: {promote_r.status_code} {promote_r.json()}")
|
|
|
|
# Re-login
|
|
master_token = client.post("/login", json={
|
|
"email": master_email, "password": master_password
|
|
}).json()["access_token"]
|
|
else:
|
|
master_token = login_r.json()["access_token"]
|
|
print(f"\n[DEBUG] master déjà existant, token ok")
|
|
|
|
# Vérifie que master est bien admin
|
|
stats_r = client.get(f"/admin/stats?token={master_token}")
|
|
print(f"[DEBUG] stats avec master token: {stats_r.status_code} {stats_r.json()}")
|
|
|
|
return master_token, master_email
|
|
class TestAdminStats:
|
|
"""Tests de GET /admin/stats"""
|
|
|
|
def test_sans_token_401(self):
|
|
skip_no_app()
|
|
r = client.get("/admin/stats?token=faux")
|
|
assert r.status_code in [401, 403]
|
|
|
|
def test_avec_token_user_403(self):
|
|
skip_no_app()
|
|
email = f"user_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
token = client.post("/login", json={"email": email, "password": "test123"}).json()["access_token"]
|
|
r = client.get(f"/admin/stats?token={token}")
|
|
assert r.status_code == 403
|
|
|
|
def test_avec_token_admin_retourne_stats(self):
|
|
skip_no_app()
|
|
token, _ = get_admin_token()
|
|
r = client.get(f"/admin/stats?token={token}")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert "total_documents" in data
|
|
assert "cir_documents" in data
|
|
assert "sccs_documents" in data
|
|
assert "total_users" in data
|
|
|
|
|
|
class TestAdminUsers:
|
|
"""Tests de GET /admin/users et DELETE /admin/users/{id}"""
|
|
|
|
def test_get_users_sans_admin_403(self):
|
|
skip_no_app()
|
|
email = f"user_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
token = client.post("/login", json={"email": email, "password": "test123"}).json()["access_token"]
|
|
r = client.get(f"/admin/users?token={token}")
|
|
assert r.status_code == 403
|
|
|
|
def test_get_users_avec_admin_retourne_liste(self):
|
|
skip_no_app()
|
|
token, _ = get_admin_token()
|
|
r = client.get(f"/admin/users?token={token}")
|
|
assert r.status_code == 200
|
|
assert isinstance(r.json(), list)
|
|
|
|
def test_delete_user_inexistant_404(self):
|
|
skip_no_app()
|
|
token, _ = get_admin_token()
|
|
r = client.delete(f"/admin/users/999999?token={token}")
|
|
assert r.status_code == 404
|
|
|
|
def test_patch_role_user_inexistant_404(self):
|
|
skip_no_app()
|
|
token, _ = get_admin_token()
|
|
r = client.patch(f"/admin/users/999999/role?token={token}", json={"role": "admin"})
|
|
assert r.status_code == 404
|
|
|
|
|
|
class TestAdminDocuments:
|
|
"""Tests de DELETE /admin/documents/{id}"""
|
|
|
|
def test_delete_document_sans_admin_403(self):
|
|
skip_no_app()
|
|
email = f"user_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
token = client.post("/login", json={"email": email, "password": "test123"}).json()["access_token"]
|
|
r = client.delete(f"/admin/documents/1?token={token}")
|
|
assert r.status_code == 403
|
|
|
|
def test_delete_document_inexistant_404(self):
|
|
skip_no_app()
|
|
token, _ = get_admin_token()
|
|
r = client.delete(f"/admin/documents/999999?token={token}")
|
|
assert r.status_code == 404
|
|
|
|
|
|
class TestAdminMakeAdmin:
|
|
"""Tests de POST /admin/make-admin"""
|
|
|
|
def test_make_admin_utilisateur_inexistant_404(self):
|
|
skip_no_app()
|
|
token, _ = get_admin_token()
|
|
r = client.post(f"/admin/make-admin?token={token}&target_email=inexistant@regwatch.fr")
|
|
assert r.status_code == 404
|
|
|
|
def test_make_admin_retourne_message(self):
|
|
skip_no_app()
|
|
token, _ = get_admin_token()
|
|
email = f"futuradmin_{int(time.time())}@regwatch.fr"
|
|
client.post("/signup", json={"email": email, "password": "test123", "full_name": "T"})
|
|
r = client.post(f"/admin/make-admin?token={token}&target_email={email}")
|
|
assert r.status_code == 200
|
|
assert "admin" in r.json()["message"].lower() |