diff --git a/tests/demo/__init__.py b/tests/demo/__init__.py
new file mode 100644
index 000000000..00d9381de
--- /dev/null
+++ b/tests/demo/__init__.py
@@ -0,0 +1 @@
+# Demo package
diff --git a/tests/demo/demo_reset_noms.py b/tests/demo/demo_reset_noms.py
index 26ee2a6c1..733739ea6 100755
--- a/tests/demo/demo_reset_noms.py
+++ b/tests/demo/demo_reset_noms.py
@@ -15,6 +15,8 @@ import sys
import random
import psycopg2
+from gen_nomprenoms import nomprenom
+
def usage():
print(f"Usage: {sys.argv[0]} dbname formsemestre_id")
@@ -28,21 +30,6 @@ dbname = sys.argv[1]
formsemestre_id = sys.argv[2]
DBCNXSTRING = f"dbname={dbname}"
-# Noms et prénoms les plus fréquents en France:
-NOMS = [x.strip() for x in open("noms.txt").readlines()]
-PRENOMS_H = [x.strip() for x in open("prenoms-h.txt").readlines()]
-PRENOMS_F = [x.strip() for x in open("prenoms-f.txt").readlines()]
-
-
-def nomprenom(sexe):
- """un nom et un prenom au hasard"""
- if "e" in sexe.lower():
- prenom = random.choice(PRENOMS_F)
- else:
- prenom = random.choice(PRENOMS_H)
- return random.choice(NOMS), prenom
-
-
# Liste des etudiants inscrits à ce semestre
cnx = psycopg2.connect(DBCNXSTRING)
cursor = cnx.cursor()
diff --git a/tests/demo/gen_nomprenoms.py b/tests/demo/gen_nomprenoms.py
new file mode 100644
index 000000000..e6103fc50
--- /dev/null
+++ b/tests/demo/gen_nomprenoms.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+import os
+import random
+from pathlib import Path
+
+cur_dir = Path(os.path.abspath(__file__)).parent
+
+# Noms et prénoms les plus fréquents en France:
+NOMS = [x.strip() for x in open(cur_dir / "noms.txt").readlines()]
+PRENOMS_H = [x.strip() for x in open(cur_dir / "prenoms-h.txt").readlines()]
+PRENOMS_F = [x.strip() for x in open(cur_dir / "prenoms-f.txt").readlines()]
+
+
+def nomprenom(sexe):
+ """un nom et un prenom au hasard"""
+ if "e" in sexe.lower() or "f" in sexe.lower():
+ prenom = random.choice(PRENOMS_F)
+ else:
+ prenom = random.choice(PRENOMS_H)
+ return random.choice(NOMS), prenom
diff --git a/tests/fakeportal/etapes.xml b/tests/fakeportal/etapes.xml
new file mode 100644
index 000000000..d832c86c7
--- /dev/null
+++ b/tests/fakeportal/etapes.xml
@@ -0,0 +1,17 @@
+
+
+ DUT RESEAUX ET TELECOMMUNICATIONS an1
+ DUT Reseaux et Telecommunications an2
+ DUT Réseaux et Télécommunications an2 (AP)
+ Certificat de Competences en Anglais niveau 1
+ PERIODE DE CESURE 1ER CYCLE IUT VILLETANEUSE
+ DIU Enseigner l'informatique au lycee
+ DU SCALA DATA ANALYST
+ DU Techniques & gestion de projets en ingenierie ferroviaire
+ AUDITEUR LIBRE A L'IUT DE VILLETANEUSE
+ Certficat informatique et internet niv 1
+ DUT CARRIERES JURIDIQUES an1
+ DUT CARRIERES JURIDIQUES an1 (CP)
+ DUT CARRIERES JURIDIQUES an1 (AP)
+ DUT CARRIERES JURIDIQUES (FCTP) an1
+
diff --git a/tests/fakeportal/etud_minimal_template.xml b/tests/fakeportal/etud_minimal_template.xml
new file mode 100644
index 000000000..2c969615b
--- /dev/null
+++ b/tests/fakeportal/etud_minimal_template.xml
@@ -0,0 +1,12 @@
+
+ {nip}
+ {etape}
+ {gender}
+ {nom}
+
+ {prenom}
+ {prenom} {nom}
+ 23/08/2001
+ {annee}
+ {prenom}.martin@edu.sco.zzz
+
diff --git a/tests/fakeportal/etud_template.xml b/tests/fakeportal/etud_template.xml
new file mode 100644
index 000000000..046b77a1e
--- /dev/null
+++ b/tests/fakeportal/etud_template.xml
@@ -0,0 +1,38 @@
+
+ {nip}
+ {etape}
+ {etape}/117
+ {diplome}/117
+ {gender}
+ {nom}
+
+ {prenom}
+ 23/08/2001
+ {annee}
+ 11, rue Simon Crubellier
+ 75017
+ PARIS
+ FRANCE
+ 0606060606
+ {prenom}.martin@edu.sco.zzz
+ S Scientifique
+ 2019
+ 07981234T
+ AB
+ N
+ true
+
+ MEAUX
+ 099
+ Etranger
+ 01
+ 1
+ N
+
+
+ NO
+ {diplome}
+ {etape}
+ O
+ {prenom} {nom}
+
diff --git a/tests/fakeportal/fakeportal.py b/tests/fakeportal/fakeportal.py
new file mode 100755
index 000000000..60ae09146
--- /dev/null
+++ b/tests/fakeportal/fakeportal.py
@@ -0,0 +1,127 @@
+#!/usr/bin/python3
+
+"""Simple fake HTTP serveur
+ emulating "Apogee" Web service
+"""
+import os
+import sys
+import random
+import time
+import http.server
+import socketserver
+from urllib.parse import urlparse
+from urllib.parse import parse_qs
+
+sys.path.append("..")
+from demo.gen_nomprenoms import nomprenom
+
+# Etudiant avec tous les champs (USPN)
+ETUD_TEMPLATE_FULL = open("etud_template.xml").read()
+# Etudiant avec seulement les champs requis
+ETUD_TEMPLATE_MINI = open("etud_minimal_template.xml").read()
+
+ETUD_HEAD = """
+"""
+ETUD_TAIL = """
+"""
+
+
+def make_random_etud(nip, etape=None, annee=None, template=ETUD_TEMPLATE_FULL):
+ """return XML for a student"""
+ random.seed(nip) # deterministic choice based on nip
+ gender = random.choice(("M", "F"))
+ nom, prenom = nomprenom(gender)
+ if not etape:
+ etape = random.choice(("V1RT", "V2RT", "V2RT2", ""))
+ if not annee:
+ annee = time.strftime("%Y") # current year
+ diplome = "VDRT"
+ data = template.format(
+ nip=nip,
+ gender=gender,
+ nom=nom,
+ prenom=prenom,
+ etape=etape,
+ diplome=diplome,
+ annee=annee,
+ )
+ return data
+
+
+def make_random_etape_etuds(etape, annee):
+ """Liste d'etudiants d'une etape"""
+ random.seed(etape + annee)
+ nb = random.randint(0, 50)
+ L = []
+ for i in range(nb):
+ if i % 2:
+ template = ETUD_TEMPLATE_MINI
+ else:
+ template = ETUD_TEMPLATE_FULL
+ nip = str(random.randint(10000000, 99999999)) # 8 digits
+ L.append(make_random_etud(nip, etape=etape, annee=annee, template=template))
+ return "\n".join(L)
+
+
+class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
+ def send_xml(self, data):
+ self.send_response(200)
+ self.send_header("Content-type", "text/xml;charset=UTF-8")
+ self.end_headers()
+ self.wfile.write(bytes(data, "utf8"))
+
+ def do_GET(self):
+ query_components = parse_qs(urlparse(self.path).query)
+ print(f"path={self.path}", file=sys.stderr)
+ print(query_components, file=sys.stderr)
+
+ if "etapes" in self.path.lower():
+ self.path = "etapes.xml"
+ elif "scodocEtudiant" in self.path:
+ # 2 forms: nip=xxx or etape=eee&annee=aaa
+ if "nip" in query_components:
+ nip = query_components["nip"][0]
+ print(f"requesting nip={nip}")
+ data = ETUD_HEAD + make_random_etud(nip) + ETUD_TAIL
+ return self.send_xml(data)
+ elif "etape" in query_components:
+ etape = query_components["etape"][0]
+ print(f"requesting etape={etape}")
+ if "annee" in query_components:
+ annee = query_components["annee"][0]
+ data = ETUD_HEAD + make_random_etape_etuds(etape, annee) + ETUD_TAIL
+ return self.send_xml(data)
+ else:
+ print(
+ f"Error 404: (missing annee) path={self.path}", file=sys.stderr
+ )
+ self.send_response(404)
+ return
+ else:
+ print(
+ f"Error 404: (missing nip or etape) path={self.path}",
+ file=sys.stderr,
+ )
+ self.send_response(404)
+ return
+ else:
+ print(f"Error 404: path={self.path}")
+ self.send_response(404)
+ return
+
+ # Sending an '200 OK' response
+ self.send_response(200)
+ http.server.SimpleHTTPRequestHandler.do_GET(self)
+
+ return
+
+
+# Create an object of the above class
+handler_object = MyHttpRequestHandler
+
+PORT = 8678
+my_server = socketserver.TCPServer(("", PORT), handler_object)
+
+if __name__ == "__main__":
+ # Start the server
+ my_server.serve_forever()