2022-03-09 16:52:07 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2022-05-04 23:11:20 +02:00
|
|
|
"""Test API
|
2022-03-09 16:52:07 +01:00
|
|
|
|
|
|
|
Utilisation :
|
|
|
|
créer les variables d'environnement: (indiquer les valeurs
|
|
|
|
pour le serveur ScoDoc que vous voulez interroger)
|
|
|
|
|
|
|
|
export SCODOC_URL="https://scodoc.xxx.net/"
|
2022-05-04 23:11:20 +02:00
|
|
|
export API_USER="xxx"
|
2022-03-09 16:52:07 +01:00
|
|
|
export SCODOC_PASSWD="xxx"
|
|
|
|
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
|
|
|
|
|
|
|
|
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
|
|
|
|
"""
|
|
|
|
import os
|
|
|
|
import requests
|
2024-01-22 09:57:41 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
except ModuleNotFoundError:
|
|
|
|
print("\nWarning: dotenv not installed, ignoring .env")
|
|
|
|
print("You may install it using:\npip install python-dotenv\n")
|
|
|
|
load_dotenv = None
|
|
|
|
try:
|
|
|
|
import pytest
|
|
|
|
except ModuleNotFoundError:
|
|
|
|
print("pytest not installed\n")
|
|
|
|
pytest = None
|
2022-05-04 23:11:20 +02:00
|
|
|
|
2022-08-10 10:49:58 +02:00
|
|
|
# --- Lecture configuration (variables d'env ou .env)
|
|
|
|
try:
|
|
|
|
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
except NameError:
|
|
|
|
BASEDIR = "/opt/scodoc/tests/api"
|
|
|
|
|
2024-01-22 09:57:41 +01:00
|
|
|
if load_dotenv:
|
|
|
|
load_dotenv(os.path.join(BASEDIR, ".env"))
|
|
|
|
|
2022-05-04 23:11:20 +02:00
|
|
|
CHECK_CERTIFICATE = bool(os.environ.get("CHECK_CERTIFICATE", False))
|
2024-01-22 09:57:41 +01:00
|
|
|
SCODOC_URL = os.environ.get("SCODOC_URL") or "http://localhost:5000"
|
2022-05-04 23:11:20 +02:00
|
|
|
API_URL = SCODOC_URL + "/ScoDoc/api"
|
|
|
|
API_USER = os.environ.get("API_USER", "test")
|
2022-10-07 22:37:06 +02:00
|
|
|
API_PASSWORD = os.environ.get("API_PASSWORD", os.environ.get("API_PASSWD", "test"))
|
2022-08-05 06:55:05 +02:00
|
|
|
API_USER_ADMIN = os.environ.get("API_USER_ADMIN", "admin_api")
|
|
|
|
API_PASSWORD_ADMIN = os.environ.get("API_PASSWD_ADMIN", "admin_api")
|
2022-05-11 04:14:42 +02:00
|
|
|
DEPT_ACRONYM = "TAPI"
|
2024-09-21 16:42:41 +02:00
|
|
|
API_DEPT_URL = f"{SCODOC_URL}/ScoDoc/{DEPT_ACRONYM}/api"
|
2023-04-06 16:10:32 +02:00
|
|
|
SCO_TEST_API_TIMEOUT = 5
|
2022-05-04 23:11:20 +02:00
|
|
|
print(f"SCODOC_URL={SCODOC_URL}")
|
|
|
|
print(f"API URL={API_URL}")
|
2024-01-22 09:57:41 +01:00
|
|
|
print(f"API_USER={API_USER}")
|
2022-05-04 23:11:20 +02:00
|
|
|
|
|
|
|
|
2022-08-08 10:34:13 +02:00
|
|
|
class APIError(Exception):
|
2024-08-14 10:47:48 +02:00
|
|
|
def __init__(self, message: str = "", payload=None, status_code=None):
|
2022-08-17 18:15:48 +02:00
|
|
|
self.message = message
|
|
|
|
self.payload = payload or {}
|
2024-08-14 10:47:48 +02:00
|
|
|
self.status_code = status_code
|
2022-08-08 10:34:13 +02:00
|
|
|
|
2024-08-06 22:30:30 +02:00
|
|
|
def __str__(self):
|
2024-08-14 10:47:48 +02:00
|
|
|
return f"APIError: {self.message} payload={self.payload} status_code={self.status_code}"
|
2024-08-06 22:30:30 +02:00
|
|
|
|
2022-08-08 10:34:13 +02:00
|
|
|
|
2022-08-05 06:55:05 +02:00
|
|
|
def get_auth_headers(user, password) -> dict:
|
|
|
|
"Demande de jeton, dict à utiliser dans les en-têtes de requêtes http"
|
2023-04-06 10:38:31 +02:00
|
|
|
ans = requests.post(API_URL + "/tokens", auth=(user, password), timeout=5)
|
2022-08-08 10:34:13 +02:00
|
|
|
if ans.status_code != 200:
|
2024-08-14 10:47:48 +02:00
|
|
|
raise APIError(f"Echec demande jeton par {user}", status_code=ans.status_code)
|
2022-08-08 10:34:13 +02:00
|
|
|
token = ans.json()["token"]
|
2022-04-13 12:39:10 +02:00
|
|
|
return {"Authorization": f"Bearer {token}"}
|
2022-08-01 21:42:19 +02:00
|
|
|
|
|
|
|
|
2024-01-22 09:57:41 +01:00
|
|
|
if pytest:
|
2022-08-05 06:55:05 +02:00
|
|
|
|
2024-01-22 09:57:41 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def api_headers() -> dict:
|
|
|
|
"""Jeton, utilisateur API ordinaire"""
|
|
|
|
return get_auth_headers(API_USER, API_PASSWORD)
|
2022-08-05 06:55:05 +02:00
|
|
|
|
2024-01-22 09:57:41 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def api_admin_headers() -> dict:
|
|
|
|
"""Jeton, utilisateur API SuperAdmin"""
|
|
|
|
return get_auth_headers(API_USER_ADMIN, API_PASSWORD_ADMIN)
|
2022-08-05 06:55:05 +02:00
|
|
|
|
|
|
|
|
2024-07-27 13:30:02 +02:00
|
|
|
class _DefaultHeaders:
|
|
|
|
headers = {}
|
|
|
|
|
|
|
|
|
|
|
|
def set_headers(headers: dict):
|
|
|
|
"""Set default headers"""
|
|
|
|
print(f"set_headers: {headers}")
|
|
|
|
_DefaultHeaders.headers = headers
|
|
|
|
|
|
|
|
|
2024-09-21 16:42:41 +02:00
|
|
|
def GET(
|
|
|
|
path: str, headers: dict = None, errmsg=None, dept: str | None = None, raw=False
|
|
|
|
):
|
2024-03-19 18:22:02 +01:00
|
|
|
"""Get and optionaly returns as JSON
|
2023-03-09 14:24:12 +01:00
|
|
|
Special case for non json result (image or pdf):
|
|
|
|
return Content-Disposition string (inline or attachment)
|
2024-03-19 18:22:02 +01:00
|
|
|
If raw, return a requests.Response
|
2022-08-21 09:17:45 +02:00
|
|
|
"""
|
2022-08-01 21:42:19 +02:00
|
|
|
if dept:
|
|
|
|
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
|
|
|
else:
|
|
|
|
url = API_URL + path
|
2023-04-06 10:38:31 +02:00
|
|
|
reply = requests.get(
|
|
|
|
url,
|
2024-07-27 13:30:02 +02:00
|
|
|
headers=_DefaultHeaders.headers if headers is None else headers,
|
2023-04-06 10:38:31 +02:00
|
|
|
verify=CHECK_CERTIFICATE,
|
2023-04-06 16:10:32 +02:00
|
|
|
timeout=SCO_TEST_API_TIMEOUT,
|
2023-04-06 10:38:31 +02:00
|
|
|
)
|
2023-03-09 14:24:12 +01:00
|
|
|
if reply.status_code != 200:
|
2024-01-05 13:42:55 +01:00
|
|
|
print("url", url)
|
|
|
|
print("reply", reply.text)
|
2023-03-09 14:24:12 +01:00
|
|
|
raise APIError(
|
2024-08-14 10:47:48 +02:00
|
|
|
errmsg or f"""erreur get {url} !""",
|
2024-10-14 16:40:05 +02:00
|
|
|
reply if reply.status_code == 404 else reply.json(),
|
2024-08-14 10:47:48 +02:00
|
|
|
status_code=reply.status_code,
|
2023-03-09 14:24:12 +01:00
|
|
|
)
|
2024-03-19 18:22:02 +01:00
|
|
|
if raw:
|
|
|
|
return reply
|
2023-03-09 14:24:12 +01:00
|
|
|
if reply.headers.get("Content-Type", None) == "application/json":
|
|
|
|
return reply.json() # decode la reponse JSON
|
2024-03-19 18:22:02 +01:00
|
|
|
if reply.headers.get("Content-Type", None) in [
|
2022-08-21 09:17:45 +02:00
|
|
|
"application/pdf",
|
2024-08-14 15:39:57 +02:00
|
|
|
"application/vnd.ms-excel",
|
|
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
|
|
"image/gif",
|
|
|
|
"image/jpeg",
|
|
|
|
"image/png",
|
|
|
|
"image/webp",
|
2022-08-21 09:17:45 +02:00
|
|
|
]:
|
|
|
|
retval = {
|
2023-03-09 14:24:12 +01:00
|
|
|
"Content-Type": reply.headers.get("Content-Type", None),
|
|
|
|
"Content-Disposition": reply.headers.get("Content-Disposition", None),
|
2022-08-21 09:17:45 +02:00
|
|
|
}
|
|
|
|
return retval
|
2024-08-14 10:47:48 +02:00
|
|
|
raise APIError(
|
2024-08-14 15:39:57 +02:00
|
|
|
f"Unknown returned content {reply.headers.get('Content-Type', None)} !\n",
|
2024-08-14 10:47:48 +02:00
|
|
|
status_code=reply.status_code,
|
|
|
|
)
|
2022-08-01 21:42:19 +02:00
|
|
|
|
|
|
|
|
2024-07-27 13:30:02 +02:00
|
|
|
def POST(
|
2024-08-06 22:30:30 +02:00
|
|
|
path: str,
|
|
|
|
data: dict = None,
|
|
|
|
headers: dict = None,
|
|
|
|
errmsg=None,
|
2024-09-21 16:42:41 +02:00
|
|
|
dept: str | None = None,
|
2024-08-06 22:30:30 +02:00
|
|
|
raw=False,
|
2024-06-21 00:53:52 +02:00
|
|
|
):
|
|
|
|
"""Post
|
|
|
|
Decode réponse en json, sauf si raw.
|
|
|
|
"""
|
2024-08-06 22:30:30 +02:00
|
|
|
data = data or {}
|
2022-08-19 12:46:21 +02:00
|
|
|
if dept:
|
|
|
|
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
|
|
|
else:
|
|
|
|
url = API_URL + path
|
2022-08-01 21:42:19 +02:00
|
|
|
r = requests.post(
|
2022-08-19 12:46:21 +02:00
|
|
|
url,
|
2022-08-01 21:42:19 +02:00
|
|
|
json=data,
|
2024-07-27 13:30:02 +02:00
|
|
|
headers=_DefaultHeaders.headers if headers is None else headers,
|
2022-08-01 21:42:19 +02:00
|
|
|
verify=CHECK_CERTIFICATE,
|
2023-04-06 16:10:32 +02:00
|
|
|
timeout=SCO_TEST_API_TIMEOUT,
|
2022-08-01 21:42:19 +02:00
|
|
|
)
|
|
|
|
if r.status_code != 200:
|
2024-08-06 22:30:30 +02:00
|
|
|
try:
|
|
|
|
payload = r.json()
|
|
|
|
except requests.exceptions.JSONDecodeError:
|
|
|
|
payload = r.text
|
|
|
|
raise APIError(
|
2024-08-14 10:47:48 +02:00
|
|
|
errmsg or f"erreur url={url} status={r.status_code} !",
|
|
|
|
payload=payload,
|
|
|
|
status_code=r.status_code,
|
2024-08-06 22:30:30 +02:00
|
|
|
)
|
2024-06-21 00:53:52 +02:00
|
|
|
return r if raw else r.json() # decode la reponse JSON
|
2023-08-25 17:58:57 +02:00
|
|
|
|
|
|
|
|
|
|
|
def check_fields(data: dict, fields: dict = None):
|
|
|
|
"""
|
|
|
|
Vérifie que le dictionnaire data contient les bonnes clés
|
|
|
|
et les bons types de valeurs.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
data (dict): un dictionnaire (json de retour de l'api)
|
|
|
|
fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
|
|
|
|
"""
|
|
|
|
assert set(data.keys()) == set(fields.keys())
|
|
|
|
for key in data:
|
2023-09-27 23:14:45 +02:00
|
|
|
if key in ("moduleimpl_id", "desc", "external_data"):
|
2023-08-25 17:58:57 +02:00
|
|
|
assert (
|
|
|
|
isinstance(data[key], fields[key]) or data[key] is None
|
|
|
|
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
|
|
|
|
else:
|
|
|
|
assert isinstance(
|
|
|
|
data[key], fields[key]
|
|
|
|
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
|
|
|
|
|
|
|
|
|
|
|
|
def check_failure_get(path: str, headers: dict, err: str = None):
|
|
|
|
"""
|
|
|
|
Vérifie que la requête GET renvoie bien un 404
|
|
|
|
|
|
|
|
Args:
|
|
|
|
path (str): la route de l'api
|
|
|
|
headers (dict): le token d'auth de l'api
|
|
|
|
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
|
|
|
"""
|
|
|
|
|
|
|
|
try:
|
2024-01-05 13:42:55 +01:00
|
|
|
GET(path=path, headers=headers, dept=DEPT_ACRONYM)
|
2023-08-25 17:58:57 +02:00
|
|
|
# ^ Renvoi un 404
|
|
|
|
except APIError as api_err:
|
|
|
|
if err is not None:
|
2024-10-14 16:40:05 +02:00
|
|
|
if "message" in api_err.payload:
|
|
|
|
assert api_err.payload["message"] == err
|
2023-08-25 17:58:57 +02:00
|
|
|
else:
|
|
|
|
raise APIError("Le GET n'aurait pas du fonctionner")
|
|
|
|
|
|
|
|
|
|
|
|
def check_failure_post(path: str, headers: dict, data: dict, err: str = None):
|
|
|
|
"""
|
|
|
|
Vérifie que la requête POST renvoie bien un 404
|
|
|
|
|
|
|
|
Args:
|
|
|
|
path (str): la route de l'api
|
|
|
|
headers (dict): le token d'auth
|
|
|
|
data (dict): un dictionnaire (json) à envoyer
|
|
|
|
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
|
|
|
"""
|
|
|
|
|
|
|
|
try:
|
2024-07-27 13:30:02 +02:00
|
|
|
data = POST(path=path, headers=headers, data=data, dept=DEPT_ACRONYM)
|
2023-08-25 17:58:57 +02:00
|
|
|
# ^ Renvoie un 404
|
|
|
|
except APIError as api_err:
|
|
|
|
if err is not None:
|
|
|
|
assert (
|
|
|
|
api_err.payload["message"] == err
|
|
|
|
), f"received: {api_err.payload['message']}"
|
|
|
|
else:
|
|
|
|
raise APIError("Le GET n'aurait pas du fonctionner")
|