Merge branch 'master' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises

This commit is contained in:
Arthur ZHU 2022-08-23 18:55:01 +02:00
commit 8e730c2164
9 changed files with 408 additions and 66 deletions

View File

@ -46,23 +46,16 @@ from app.scodoc.sco_permissions import Permission
@bp.route("/logos") @bp.route("/logos")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoSuperAdmin)
def api_get_glob_logos(): def api_get_glob_logos():
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
return json_error(403, message="accès interdit")
required_format = requested_format() # json only
if required_format is None:
return json_error(400, "Illegal format")
logos = list_logos()[None] logos = list_logos()[None]
return jsonify(list(logos.keys())) return jsonify(list(logos.keys()))
@bp.route("/logos/<string:logoname>") @bp.route("/logo/<string:logoname>")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoSuperAdmin)
def api_get_glob_logo(logoname): def api_get_glob_logo(logoname):
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
return json_error(403, message="accès interdit")
logo = find_logo(logoname=logoname) logo = find_logo(logoname=logoname)
if logo is None: if logo is None:
return json_error(404, message="logo not found") return json_error(404, message="logo not found")
@ -74,25 +67,27 @@ def api_get_glob_logo(logoname):
) )
@bp.route("/departements/<string:departement>/logos") def core_get_logos(dept_id):
@scodoc
@permission_required(Permission.ScoView)
def api_get_local_logos(departement):
dept_id = Departement.from_acronym(departement).id
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
return json_error(403, message="accès interdit")
logos = list_logos().get(dept_id, dict()) logos = list_logos().get(dept_id, dict())
return jsonify(list(logos.keys())) return jsonify(list(logos.keys()))
@bp.route("/departements/<string:departement>/logos/<string:logoname>") @bp.route("/departement/<string:departement>/logos")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoSuperAdmin)
def api_get_local_logo(departement, logoname): def api_get_local_logos_by_acronym(departement):
# format = requested_format("jpg", ['png', 'jpg']) XXX ?
dept_id = Departement.from_acronym(departement).id dept_id = Departement.from_acronym(departement).id
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement): return core_get_logos(dept_id)
return json_error(403, message="accès interdit")
@bp.route("/departement/id/<int:dept_id>/logos")
@scodoc
@permission_required(Permission.ScoSuperAdmin)
def api_get_local_logos_by_id(dept_id):
return core_get_logos(dept_id)
def core_get_logo(dept_id, logoname):
logo = find_logo(logoname=logoname, dept_id=dept_id) logo = find_logo(logoname=logoname, dept_id=dept_id)
if logo is None: if logo is None:
return json_error(404, message="logo not found") return json_error(404, message="logo not found")
@ -102,3 +97,18 @@ def api_get_local_logo(departement, logoname):
mimetype=f"image/{logo.suffix}", mimetype=f"image/{logo.suffix}",
last_modified=datetime.now(), last_modified=datetime.now(),
) )
@bp.route("/departement/<string:departement>/logo/<string:logoname>")
@scodoc
@permission_required(Permission.ScoSuperAdmin)
def api_get_local_logo_dept_by_acronym(departement, logoname):
dept_id = Departement.from_acronym(departement).id
return core_get_logo(dept_id, logoname)
@bp.route("/departement/id/<int:dept_id>/logo/<string:logoname>")
@scodoc
@permission_required(Permission.ScoSuperAdmin)
def api_get_local_logo_dept_by_id(dept_id, logoname):
return core_get_logo(dept_id, logoname)

View File

@ -14,9 +14,9 @@ from flask_login import current_user, login_required
from app import db, log from app import db, log
from app.api import api_bp as bp, api_web_bp from app.api import api_bp as bp, api_web_bp
from app.models.etudiants import Identite
from app.scodoc.sco_utils import json_error from app.scodoc.sco_utils import json_error
from app.auth.models import User, Role, UserRole from app.auth.models import User, Role, UserRole
from app.auth.models import is_valid_password
from app.decorators import scodoc, permission_required from app.decorators import scodoc, permission_required
from app.models import Departement from app.models import Departement
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
@ -38,7 +38,7 @@ def user_info(uid: int):
return json_error(404, "user not found") return json_error(404, "user not found")
if g.scodoc_dept: if g.scodoc_dept:
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersView) allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersView)
if user.dept not in allowed_depts: if (None not in allowed_depts) and (user.dept not in allowed_depts):
return json_error(404, "user not found") return json_error(404, "user not found")
return jsonify(user.to_dict()) return jsonify(user.to_dict())
@ -109,7 +109,7 @@ def user_create():
if dept == "@all": if dept == "@all":
dept = None dept = None
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin) allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin)
if dept not in allowed_depts: if (None not in allowed_depts) and (dept not in allowed_depts):
return json_error(403, "user_create: departement non autorise") return json_error(403, "user_create: departement non autorise")
if (dept is not None) and ( if (dept is not None) and (
Departement.query.filter_by(acronym=dept).first() is None Departement.query.filter_by(acronym=dept).first() is None
@ -168,6 +168,35 @@ def user_edit(uid: int):
return jsonify(user.to_dict()) return jsonify(user.to_dict())
@bp.route("/user/<int:uid>/password", methods=["POST"])
@api_web_bp.route("/user/<int:uid>/password", methods=["POST"])
@login_required
@scodoc
@permission_required(Permission.ScoUsersAdmin)
def user_password(uid: int):
"""Modification du mot de passe d'un utilisateur
Champs modifiables:
{
"password": str
}
Si le mot de passe ne convient pas, erreur 400.
"""
data = request.get_json(force=True) # may raise 400 Bad Request
user: User = User.query.get_or_404(uid)
password = data.get("password")
if not password:
return json_error(404, "user_password: missing password")
if not is_valid_password(password):
return json_error(400, "user_password: invalid password")
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin)
if (None not in allowed_depts) and ((user.dept not in allowed_depts)):
return json_error(403, "user_password: departement non autorise")
user.set_password(password)
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict())
@bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"]) @bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"])
@api_web_bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"]) @api_web_bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"])
@bp.route( @bp.route(

View File

@ -51,6 +51,7 @@ print(f"API URL={API_URL}")
HEADERS = get_auth_headers(API_USER, API_PASSWORD) HEADERS = get_auth_headers(API_USER, API_PASSWORD)
admin_h = get_auth_headers("admin_api", "admin_api")
departements = GET("/departements", headers=HEADERS) departements = GET("/departements", headers=HEADERS)
pp(departements) pp(departements)

View File

@ -6,8 +6,9 @@
Usage: Usage:
cd /opt/scodoc/tests/api cd /opt/scodoc/tests/api
python make_samples.py python make_samples.py [entry_names]
si entry_names est spécifié, la génération est restreints aux exemples cités. expl: `python make_samples departements departement-formsemestres`
doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets) doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets)
cd /opt/scodoc/tests/api cd /opt/scodoc/tests/api
tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
@ -41,6 +42,8 @@ TODO: ajouter un argument au script permettant de ne générer qu'un seul fichie
""" """
import os import os
import shutil import shutil
import sys
import re
from collections import defaultdict from collections import defaultdict
from pprint import pprint as pp from pprint import pprint as pp
from pprint import pformat as pf from pprint import pformat as pf
@ -69,6 +72,7 @@ class Sample:
self.url = url self.url = url
self.method = method self.method = method
self.result = None self.result = None
self.output = "json"
if permission == "ScoView": if permission == "ScoView":
HEADERS = get_auth_headers("test", "test") HEADERS = get_auth_headers("test", "test")
elif permission == "ScoSuperAdmin": elif permission == "ScoSuperAdmin":
@ -87,16 +91,16 @@ class Sample:
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS) self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
elif self.method[0] != "#": elif self.method[0] != "#":
raise Exception(f"Bad method : {self.method}") raise Exception(f"Bad method : {self.method}")
else: # method begin with # => comment
print(" pass")
self.shorten() self.shorten()
file = open(f"sample_TEST.json.md", "tw") file = open(f"sample_TEST.json.md", "tw")
self.dump(file) self.dump(file)
file.close() file.close()
def _shorten(self, item): def _shorten(
self, item
): # abrege les longues listes (limite à 2 éléments et affiche "... etc. à la place"
if isinstance(item, list): if isinstance(item, list):
return [self._shorten(child) for child in item[:2]] return [self._shorten(child) for child in item[:2]] + ["... etc."]
return item return item
def shorten(self): def shorten(self):
@ -117,19 +121,36 @@ class Sample:
file.write(f"> `{self.content}`\n\n") file.write(f"> `{self.content}`\n\n")
file.write("```json\n") file.write("```json\n")
file.write(json.dumps(self.result, indent=4)) content = json.dumps(self.result, indent=4, sort_keys=True)
content = content.replace("... etc.", "...")
# regexp for date like: "2022-08-14T10:01:44.043869+02:00"
regexp = re.compile(
r'"(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?"'
)
content = regexp.sub('"2022-08-20T12:00:00.000000+02:00"', content)
file.write(content)
file.write("\n```\n\n") file.write("\n```\n\n")
class Samples: class Samples:
def __init__(self): def __init__(self, entry_names):
"""Entry_names: la liste des entrées à reconstruire.
si None, la totalité des lignes de samples.csv est prise en compte
"""
self.entries = defaultdict(lambda: set()) self.entries = defaultdict(lambda: set())
self.entry_names = entry_names
def add_sample(self, entry, url, method="GET", permission="ScoView", content=None): def add_sample(self, entry, url, method="GET", permission="ScoView", content=None):
show_content = "" if content == "" else f": '{content}'" if self.entry_names is None or entry in self.entry_names:
print(f"{entry:50} {method:5} {url:50} {show_content}") if method[0] == "#":
sample = Sample(url, method, permission, content) detail = "**ignored**"
self.entries[entry].add(sample) elif content == "":
detail = ""
else:
detail = f": {content}"
print(f"{entry:50} {method:5} {url:50} {detail}")
sample = Sample(url, method, permission, content)
self.entries[entry].add(sample)
def pp(self): def pp(self):
for entry, samples in self.entries.items(): for entry, samples in self.entries.items():
@ -141,12 +162,18 @@ class Samples:
for entry, samples in self.entries.items(): for entry, samples in self.entries.items():
file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw") file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw")
file.write(f"### {entry}\n\n") file.write(f"### {entry}\n\n")
for sample in samples: for sample in sorted(
samples, key=lambda s: s.url
): # sorted de façon à rendre le fichier résultat déterministe (i.e. indépendant de l ordre d arrivée des résultats)
sample.dump(file) sample.dump(file)
file.close() file.close()
def make_samples(): def make_samples():
if len(sys.argv) == 1:
entry_names = None
else:
entry_names = sys.argv[1:]
if os.path.exists(DATA_DIR): if os.path.exists(DATA_DIR):
if not os.path.isdir(DATA_DIR): if not os.path.isdir(DATA_DIR):
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire" raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
@ -157,9 +184,9 @@ def make_samples():
else: else:
os.mkdir("/tmp/samples") os.mkdir("/tmp/samples")
samples = Samples() samples = Samples(entry_names)
# samples.pp() samples_file = os.path.dirname(__file__) + "/samples.csv"
with open("samples.csv") as f: with open(samples_file) as f:
L = [x[:-1].split("\t") for x in f] L = [x[:-1].split("\t") for x in f]
for line in L[1:]: for line in L[1:]:
entry_name = line[0] entry_name = line[0]

View File

@ -26,6 +26,7 @@ etudiant_formsemestres /etudiant/nip/11/formsemestres GET
etudiant-formsemestre-bulletin /etudiant/etudid/11/formsemestre/1/bulletin GET etudiant-formsemestre-bulletin /etudiant/etudid/11/formsemestre/1/bulletin GET
etudiant-formsemestre-bulletin /etudiant/ine/INE11/formsemestre/1/bulletin GET etudiant-formsemestre-bulletin /etudiant/ine/INE11/formsemestre/1/bulletin GET
etudiant-formsemestre-bulletin /etudiant/nip/11/formsemestre/1/bulletin GET etudiant-formsemestre-bulletin /etudiant/nip/11/formsemestre/1/bulletin GET
etudiant-formsemestre-bulletin /etudiant/nip/11/formsemestre/1/bulletin/short/pdf GET
etudiant-formsemestre-groups /etudiant/etudid/11/formsemestre/1/groups GET etudiant-formsemestre-groups /etudiant/etudid/11/formsemestre/1/groups GET
formations /formations GET formations /formations GET
formations_ids /formations_ids GET formations_ids /formations_ids GET
@ -67,11 +68,30 @@ partition-groups-order /partition/1/groups/order ScoSuperAdmin POST [ 1 ]
partition-delete /partition/2/delete ScoSuperAdmin POST partition-delete /partition/2/delete ScoSuperAdmin POST
user-create /user/create ScoSuperAdmin POST {"user_name": "alain", "dept": null, "nom": "alain", "prenom": "bruno", "active": true } user-create /user/create ScoSuperAdmin POST {"user_name": "alain", "dept": null, "nom": "alain", "prenom": "bruno", "active": true }
user-edit /user/10/edit ScoSuperAdmin POST { "dept": "TAPI", "nom": "alain2", "prenom": "bruno2", "active": false } user-edit /user/10/edit ScoSuperAdmin POST { "dept": "TAPI", "nom": "alain2", "prenom": "bruno2", "active": false }
user-password /user/3/password ScoSuperAdmin POST { "password": "rePlaCemeNT456averylongandcomplicated" }
user-password /user/3/password ScoSuperAdmin POST { "password": "too_simple" }
user-role-add /user/10/role/Observateur/add ScoSuperAdmin POST user-role-add /user/10/role/Observateur/add ScoSuperAdmin POST
user-role-remove /user/10/role/Observateur/remove ScoSuperAdmin POST user-role-remove /user/10/role/Observateur/remove ScoSuperAdmin POST
role-create /role/create/customRole ScoSuperAdmin POST {"permissions": ["ScoView", "ScoUsersView"]} role-create /role/create/customRole ScoSuperAdmin POST {"permissions": ["ScoView", "ScoUsersView"]}
role-remove_permission /role/customRole/remove_permission/ScoUsersView ScoSuperAdmin POST role-remove_permission /role/customRole/remove_permission/ScoUsersView ScoSuperAdmin POST
role-add_permission /role/customRole/add_permission/ScoUsersView ScoSuperAdmin POST role-add_permission /role/customRole/add_permission/ScoUsersView ScoSuperAdmin POST
role-edit /role/customRole/edit ScoSuperAdmin POST { "name" : "LaveurDeVitres", "permissions" : [ "ScoView", "APIView" ] } role-edit /role/customRole/edit ScoSuperAdmin POST { "name" : "LaveurDeVitres", "permissions" : [ "ScoView", "APIView" ] }
role-edit /role/customRole/edit ScoSuperAdmin POST { "name" : "LaveurDeVitres", "permissions" : [ "ScoView", "APIView" ] }
role-delete /role/customRole/delete ScoSuperAdmin POST role-delete /role/customRole/delete ScoSuperAdmin POST
logos /logos ScoSuperAdmin GET
logo /logo/demo ScoSuperAdmin GET
departement-logos /departement/TAPI/logos ScoSuperAdmin GET
departement-logos /departement/id/1/logos ScoSuperAdmin GET
departement-logo /departement/TAPI/logo/demo ScoSuperAdmin GET
departement-logo /departement/id/1/logo/demo ScoSuperAdmin GET
test-pdf /etudiant/nip/11/formsemestre/1/bulletin/pdf GET
test-pdf /etudiant/nip/11/formsemestre/1/bulletin/pdf GET
test-pdf /etudiant/etudid/11/formsemestre/1/bulletin/short/pdf GET
test-pdf /etudiant/ine/INE11/formsemestre/1/bulletin/short/pdf GET
test-pdf /etudiant/nip/11/formsemestre/1/bulletin/short/pdf GET
test-pdf /etudiant/etudid/11/formsemestre/1/bulletin/pdf GET
test-pdf /etudiant/etudid/11/formsemestre/1/bulletin/short GET
test-pdf /etudiant/ine/INE11/formsemestre/1/bulletin/short GET
test-pdf /etudiant/nip/11/formsemestre/1/bulletin/short GET
test-pdf /etudiant/etudid/11/formsemestre/1/bulletin GET
test-pdf /etudiant/ine/INE11/formsemestre/1/bulletin GET
test-pdf /etudiant/nip/11/formsemestre/1/bulletin GET

Can't render this file because it contains an unexpected character in line 12 and column 60.

View File

@ -65,7 +65,9 @@ def api_admin_headers() -> dict:
def GET(path: str, headers: dict = None, errmsg=None, dept=None): def GET(path: str, headers: dict = None, errmsg=None, dept=None):
"""Get and returns as JSON""" """Get and returns as JSON
Special case for non json result (image or pdf): return Content-Disposition string (inline or attachment)
"""
if dept: if dept:
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
else: else:
@ -73,13 +75,34 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None):
r = requests.get(url, headers=headers or {}, verify=CHECK_CERTIFICATE) r = requests.get(url, headers=headers or {}, verify=CHECK_CERTIFICATE)
if r.status_code != 200: if r.status_code != 200:
raise APIError(errmsg or f"""erreur status={r.status_code} !""", r.json()) raise APIError(errmsg or f"""erreur status={r.status_code} !""", r.json())
if r.headers.get("Content-Type", None) == "application/json":
return r.json() # decode la reponse JSON
elif r.headers.get("Content-Type", None) in [
"image/jpg",
"image/png",
"application/pdf",
]:
retval = {
"Content-Type": r.headers.get("Content-Type", None),
"Content-Disposition": r.headers.get("Content-Disposition", None),
}
return retval
else:
raise APIError(
"Unknown returned content {r.headers.get('Content-Type', None} !\n"
)
return r.json() # decode la reponse JSON return r.json() # decode la reponse JSON
def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None): def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None, dept=None):
"""Post""" """Post"""
if dept:
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
else:
url = API_URL + path
r = requests.post( r = requests.post(
API_URL + path, url,
json=data, json=data,
headers=headers or {}, headers=headers or {},
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,

View File

@ -14,7 +14,7 @@ utilisation:
# Ce test a une logique très différente des autres : A UNIFIER # Ce test a une logique très différente des autres : A UNIFIER
from tests.api.setup_test_api import API_URL from tests.api.setup_test_api import API_URL, api_admin_headers, api_headers
from scodoc import app from scodoc import app
from tests.unit.config_test_logos import ( from tests.unit.config_test_logos import (
@ -26,48 +26,152 @@ from tests.unit.config_test_logos import (
) )
def test_super_access(create_super_token): def test_super_access(api_admin_headers):
""" """
Route: /logos Route: /logos
""" """
dept1, dept2, dept3, token = create_super_token headers = api_admin_headers
headers = {"Authorization": f"Bearer {token}"} with app.test_client(api_admin_headers) as client:
with app.test_client() as client:
response = client.get(API_URL + "/logos", headers=headers) response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json is not None assert response.json is not None
def test_admin_access(create_admin_token): def test_admin_access(api_headers):
""" """
Route: Route: /logos
""" """
dept1, dept2, dept3, token = create_admin_token headers = api_headers
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client: with app.test_client() as client:
response = client.get(API_URL + "/logos", headers=headers) response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 403 assert response.status_code == 401
def test_lambda_access(create_lambda_token): def test_lambda_access(api_headers):
""" """
Route: Route: /logos
""" """
dept1, dept2, dept3, token = create_lambda_token headers = api_headers
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client: with app.test_client() as client:
response = client.get(API_URL + "/logos", headers=headers) response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 403 assert response.status_code == 401
def test_initial_with_header_and_footer(create_super_token): def test_global_logos(api_admin_headers):
""" """
Route: Route:
""" """
dept1, dept2, dept3, token = create_super_token headers = api_admin_headers
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client: with app.test_client() as client:
response = client.get(API_URL + "/logos", headers=headers) response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json is not None assert response.json is not None
assert len(response.json) == 7 assert (
len(response.json) == 4
) # 4 items in fakelogo context: ['header', 'footer', 'logo_B', 'logo_C']
def test_local_by_id_logos(api_admin_headers):
"""
Route: /departement/id/1/logos
"""
headers = api_admin_headers
with app.test_client() as client:
response = client.get(API_URL + "/departement/id/1/logos", headers=headers)
assert response.status_code == 200
assert response.json is not None
assert (
len(response.json) == 2
) # 2 items in dept(1, TAPI) fakelogo context: ['logo_A', 'logo_D']
def test_local_by_name_logos(api_admin_headers):
"""
Route: /departement/TAPI/logos
"""
headers = api_admin_headers
with app.test_client() as client:
response = client.get(API_URL + "/departement/TAPI/logos", headers=headers)
assert response.status_code == 200
assert response.json is not None
assert (
len(response.json) == 2
) # 2 items in dept(1, TAPI) fakelogo context: ['logo_A', 'logo_D']
def test_local_png_by_id_logo(api_admin_headers):
"""
Route: /departement/id/1/logo/D
"""
headers = api_admin_headers
with app.test_client() as client:
response = client.get(API_URL + "/departement/id/1/logo/D", headers=headers)
assert response.status_code == 200
assert response.headers["Content-Type"] == "image/png"
assert response.headers["Content-Disposition"].startswith("inline")
assert "logo_D.png" in response.headers["Content-Disposition"]
def test_global_png_logo(api_admin_headers):
"""
Route: /logo/C
"""
headers = api_admin_headers
with app.test_client() as client:
response = client.get(API_URL + "/logo/C", headers=headers)
assert response.status_code == 200
assert response.headers["Content-Type"] == "image/png"
assert response.headers["Content-Disposition"].startswith("inline")
assert "logo_C.png" in response.headers["Content-Disposition"]
def test_global_jpg_logo(api_admin_headers):
"""
Route: /logo/B
"""
headers = api_admin_headers
with app.test_client() as client:
response = client.get(API_URL + "/logo/B", headers=headers)
assert response.status_code == 200
assert response.headers["Content-Type"] == "image/jpg"
assert response.headers["Content-Disposition"].startswith("inline")
assert "logo_B.jpg" in response.headers["Content-Disposition"]
def test_local_png_by_name_logo(api_admin_headers):
"""
Route: /departement/TAPI/logo/A
"""
headers = api_admin_headers
with app.test_client() as client:
response = client.get(API_URL + "/departement/TAPI/logo/D", headers=headers)
assert response.status_code == 200
assert response.headers["Content-Type"] == "image/png"
assert response.headers["Content-Disposition"].startswith("inline")
assert "logo_D.png" in response.headers["Content-Disposition"]
def test_local_jpg_by_id_logo(api_admin_headers):
"""
Route: /departement/id/1/logo/D
"""
headers = api_admin_headers
with app.test_client() as client:
response = client.get(API_URL + "/departement/id/1/logo/A", headers=headers)
assert response.status_code == 200
assert response.headers["Content-Type"] == "image/jpg"
assert response.headers["Content-Disposition"].startswith("inline")
assert "logo_A.jpg" in response.headers["Content-Disposition"]
def test_local_jpg_by_name_logo(api_admin_headers):
"""
Route: /departement/TAPI/logo/A
"""
headers = api_admin_headers
with app.test_client() as client:
response = client.get(API_URL + "/departement/TAPI/logo/A", headers=headers)
assert response.status_code == 200
assert response.headers["Content-Type"] == "image/jpg"
assert response.headers["Content-Disposition"].startswith("inline")
assert "logo_A.jpg" in response.headers["Content-Disposition"]

View File

@ -8,6 +8,7 @@ Utilisation :
from tests.api.setup_test_api import ( from tests.api.setup_test_api import (
API_URL, API_URL,
APIError,
CHECK_CERTIFICATE, CHECK_CERTIFICATE,
GET, GET,
POST_JSON, POST_JSON,
@ -83,7 +84,7 @@ def test_edit_users(api_admin_headers):
assert (nb_users + 1) == len(GET("/users/query", headers=admin_h)) assert (nb_users + 1) == len(GET("/users/query", headers=admin_h))
# Change le dept et rend inactif # Change le dept et rend inactif
user = POST_JSON( user = POST_JSON(
f"/user/edit/{user['id']}", f"/user/{user['id']}/edit",
{"active": False, "dept": "TAPI"}, {"active": False, "dept": "TAPI"},
headers=admin_h, headers=admin_h,
) )
@ -129,3 +130,102 @@ def test_roles(api_admin_headers):
assert set(role["permissions"]) == {"ScoView", "ScoAbsAddBillet"} assert set(role["permissions"]) == {"ScoView", "ScoAbsAddBillet"}
ans = POST_JSON("/role/Test_Y/delete", headers=admin_h) ans = POST_JSON("/role/Test_Y/delete", headers=admin_h)
assert ans["OK"] is True assert ans["OK"] is True
def test_modif_users_depts(api_admin_headers):
"""
Ce test vise à vérifier qu'un admin déclaré sur deux départements peut
bien modifier les utilisateurs de ses départements mais pas ceux des autres.
"""
admin_h = api_admin_headers
depts = GET("/departements", headers=admin_h)
assert len(depts) > 2
dept1, dept2, dept3 = depts[:3]
# On va utiliser les 3 1er dept (TAPI, AA, BB)
# On crée un nouvel utilisateur "chef2", admin dans les 2 premiers dept
# puis un utilisateur lambda, dans le dept 2 (AA)
chef2 = POST_JSON(
"/user/create",
{
"user_name": "chef2",
"nom": "Chef",
"prenom": "Test",
"dept": dept1["acronym"], # rattaché à dept1
},
headers=admin_h,
)
role_chef = POST_JSON(
"/role/create/chef",
{"permissions": ["ScoView", "ScoUsersAdmin", "ScoUsersView"]},
headers=admin_h,
)
_ = POST_JSON(
f"/user/{chef2['id']}/role/chef/add/departement/{dept1['acronym']}",
headers=admin_h,
)
_ = POST_JSON(
f"/user/{chef2['id']}/role/chef/add/departement/{dept2['acronym']}",
headers=admin_h,
)
# Un mot de passe trop simple:
ok = False
try:
_ = POST_JSON(
f"/user/{chef2['id']}/password",
{"password": "123456"},
headers=admin_h,
)
except APIError as exc:
if exc.args[1]["status"] == 400:
ok = True
assert ok
# Un "vrai" mot de passe:
chef2_password = "17HIOPpYhabb8qw'E:/jd7FFddjd"
_ = POST_JSON(
f"/user/{chef2['id']}/password",
{"password": chef2_password},
headers=admin_h,
)
# Création user lambda:
u_lambda = POST_JSON(
"/user/create",
{
"user_name": "lambda",
"nom": "Lambda",
"prenom": "Test",
"dept": dept2["acronym"],
},
headers=admin_h,
)
# Le chef va modifier u_lambda:
chef_h = get_auth_headers(chef2["user_name"], chef2_password)
# on utilise une URL avec département car on n'a pas le droit sur tous:
u = POST_JSON(
f"/user/{u_lambda['id']}/edit",
{"nom": "toto"},
headers=chef_h,
dept=dept1["acronym"],
)
assert u["nom"] == "toto"
# Dans l'autre ?
u = POST_JSON(
f"/user/{u_lambda['id']}/edit",
{"nom": "toto"},
headers=chef_h,
dept=dept2["acronym"],
)
# mais pas dans le troisième:
ok = False
try:
u = POST_JSON(
f"/user/{u_lambda['id']}/edit",
{"nom": "toto"},
headers=chef_h,
dept=dept3["acronym"],
)
except APIError as exc:
if exc.args[1]["status"] == 401:
ok = True
assert ok
# Nettoyage:

View File

@ -8,7 +8,9 @@
""" """
import datetime import datetime
import os
import random import random
import shutil
import time import time
import sys import sys
@ -45,6 +47,10 @@ REFCOMP_FILENAME = (
"ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml" "ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml"
) )
# la réserve de logos
LOGOS_STOCK = "/opt/scodoc/tests/ressources/test_logos/"
LOGOS_DIR = "/opt/scodoc-data/config/logos/"
def create_departements(acronyms: list[str]) -> list[Departement]: def create_departements(acronyms: list[str]) -> list[Departement]:
"Create depts" "Create depts"
@ -353,6 +359,27 @@ def create_etape_apo(formsemestre: FormSemestre):
db.session.commit() db.session.commit()
def create_logos():
if not os.path.exists(LOGOS_DIR + "logos_1"):
os.mkdir(LOGOS_DIR + "logos_1")
shutil.copy(
LOGOS_STOCK + "logo_A.jpg",
LOGOS_DIR + "logos_1/logo_A.jpg",
)
shutil.copy(
LOGOS_STOCK + "logo_D.png",
LOGOS_DIR + "logos_1/logo_D.png",
)
shutil.copy(
LOGOS_STOCK + "logo_A.jpg",
LOGOS_DIR + "logo_B.jpg",
)
shutil.copy(
LOGOS_STOCK + "logo_D.png",
LOGOS_DIR + "logo_C.png",
)
def init_test_database(): def init_test_database():
"""Appelé par la commande `flask init-test-database` """Appelé par la commande `flask init-test-database`
@ -373,6 +400,7 @@ def init_test_database():
saisie_notes_evaluations(formsemestre, user_lecteur) saisie_notes_evaluations(formsemestre, user_lecteur)
add_absences(formsemestre) add_absences(formsemestre)
create_etape_apo(formsemestre) create_etape_apo(formsemestre)
create_logos()
# à compléter # à compléter
# - groupes # - groupes
# - absences # - absences