ScoDoc/tests/api/make_samples.py
2023-04-17 15:52:05 +02:00

223 lines
7.7 KiB
Python

#!/usr/bin/env python3
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Construction des fichiers exemples pour la documentation.
Usage:
cd /opt/scodoc/tests/api
python make_samples.py [entry_names]
python make_samples.py -i <filepath> [entrynames]
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)
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
Créer éventuellement un fichier `.env` dans /opt/scodoc/tests/api
avec la config du client API:
```
SCODOC_URL = "http://localhost:5000/"
```
Cet utilitaire prend en donnée le fichier de nom `samples.csv` contenant la description des exemples (séparés par une tabulation (\t), une ligne par exemple)
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md). plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra chacun des exemples
* l'url utilisée
* la permission nécessaire (par défaut ScoView)
* la méthode GET,POST à utiliser (si commence par #, la ligne est ignorée)
* les arguments éventuel (en cas de POST): une chaîne de caractère selon json
Implémentation:
Le code complète une structure de données (Samples) qui est un dictionnaire de set (indicé par le nom des exemple.
Chacun des éléments du set est un exemple (Sample)
Quand la structure est complète, on génére tous les fichiers textes
- nom de l exemple
- un ou plusieurs exemples avec pour chaucn
- l url utilisée
- les arguments éventuels
- le résultat
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples) qui est créé ou écrasé si déjà existant
"""
import os
import shutil
import sys
import re
from collections import defaultdict
from pprint import pprint as pp
import urllib3
import json
from pandas import read_csv
from setup_test_api import (
API_PASSWORD,
API_URL,
API_USER,
APIError,
CHECK_CERTIFICATE,
get_auth_headers,
GET,
POST_JSON,
SCODOC_URL,
)
DATA_DIR = "/tmp/samples/"
SAMPLES_FILENAME = "tests/ressources/samples/samples.csv"
class Sample:
def __init__(self, url, method="GET", permission="ScoView", content=None):
self.content = content
self.permission = permission
self.url = url
self.method = method
self.result = None
self.output = "json"
if permission == "ScoView":
HEADERS = get_auth_headers("test", "test")
elif permission == "ScoSuperAdmin":
HEADERS = get_auth_headers("admin_api", "admin_api")
elif permission == "ScoUsersAdmin":
HEADERS = get_auth_headers("admin_api", "admin_api")
else:
raise Exception(f"Bad permission : {permission}")
if self.method == "GET":
self.result = GET(self.url, HEADERS)
elif self.method == "POST":
if self.content == "":
self.result = POST_JSON(self.url, headers=HEADERS)
else:
HEADERS["Content-Type"] = "application/json ; charset=utf-8"
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
elif self.method[0] != "#":
error = f'Bad method : "{self.method}"'
raise Exception(error)
self.shorten()
file = open(f"sample_TEST.json.md", "tw")
self.dump(file)
file.close()
def _shorten(
self, item
): # abrege les longues listes (limite à 2 éléments et affiche "... etc. à la place"
if isinstance(item, list):
return [self._shorten(child) for child in item[:2]] + ["... etc."]
return item
def shorten(self):
self.result = self._shorten(self.result)
def pp(self):
print(f"------ url: {self.url}")
print(f"method: {self.method}")
print(f"content: {self.content}")
print(f"permission: {self.permission}")
pp(self.result, indent=4)
def dump(self, file):
self.url = self.url.replace("?date_courante=2022-07-20", "")
file.write(f"#### {self.method} {self.url}\n")
if len(self.content) > 0:
file.write(f"> `Content-Type: application/json`\n")
file.write(f"> \n")
file.write(f"> `{self.content}`\n\n")
file.write("```json\n")
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")
class Samples:
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.entry_names = entry_names
def add_sample(self, line):
entry = line["entry_name"]
url = line["url"]
method = line["method"]
permission = line["permission"]
content = line["content"]
if self.entry_names is None or entry in self.entry_names:
if method[0] == "#":
detail = "**ignored**"
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):
for entry, samples in self.entries.items():
print(f"=== {entry}")
for sample in samples:
sample.pp()
def dump(self):
for entry, samples in self.entries.items():
file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw")
file.write(f"### {entry}\n\n")
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)
file.close()
def make_samples(samples_filename):
if len(sys.argv) == 1:
entry_names = None
elif len(sys.argv) >= 3 and sys.argv[1] == "-i":
samples_filename = sys.argv[2]
entry_names = sys.argv[3:] if len(sys.argv) > 3 else None
if os.path.exists(DATA_DIR):
if not os.path.isdir(DATA_DIR):
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
else:
# DATA_DIR existe déjà - effacer et recréer
shutil.rmtree(DATA_DIR)
os.mkdir(DATA_DIR)
else:
os.mkdir(DATA_DIR)
samples = Samples(entry_names)
df = read_csv(
samples_filename,
sep=";",
quotechar='"',
dtype={
"entry_name": str,
"url": str,
"permission": str,
"method": str,
"content": str,
},
keep_default_na=False,
)
df = df.reset_index()
df.apply(lambda line: samples.add_sample(line), axis=1)
samples.dump()
return samples
if not CHECK_CERTIFICATE:
urllib3.disable_warnings()
make_samples(SAMPLES_FILENAME)