Serveur web local sans dépendance nouvelle (http.server, Jinja2, htmx et Leaflet fournis). Écrans : Mes voyages, nouveau voyage, un voyage (journées avec carte, rattachement des traces sans heures à un jour avec suggestion d'après les photos, sources avec choix de dossier, infos et récit, génération avec avancement et arrêt), réglages du site et vérification des outils. Chaîne : jours imposés ([dates_traces] dans voyage.toml), arrêt propre de la compression, suivi des étapes, état structuré (site.tableau_etat), titre du voyage repris de voyage.toml sur l'accueil. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
101 lines
4.2 KiB
Python
101 lines
4.2 KiB
Python
"""Interface : jours imposés aux traces sans heures, suggestion d'après les
|
|
photos, et quelques échanges avec le serveur local."""
|
|
import os
|
|
import threading
|
|
import urllib.parse
|
|
import urllib.request
|
|
from datetime import date
|
|
from http.server import ThreadingHTTPServer
|
|
|
|
import pytest
|
|
|
|
from rtg import config, sources
|
|
from rtg.interface import serveur
|
|
from test_chaine import _photo, pytestmark # noqa: F401 (mêmes prérequis)
|
|
|
|
|
|
def _etape(chemin, lat0, lon0):
|
|
pts = "".join(f'<trkpt lat="{lat0 + i * 0.01:.5f}" lon="{lon0 + i * 0.012:.5f}"></trkpt>' for i in range(50))
|
|
with open(chemin, "w") as f:
|
|
f.write(f'<?xml version="1.0"?><gpx version="1.1" creator="68degres.fr" '
|
|
f'xmlns="http://www.topografix.com/GPX/1/1"><trk><trkseg>{pts}</trkseg></trk></gpx>')
|
|
|
|
|
|
@pytest.fixture
|
|
def voyage_sans_heures(tmp_path):
|
|
racine = tmp_path / "site"
|
|
voyage = racine / "Ecosse-2027"
|
|
gpx, photos = tmp_path / "gpx", tmp_path / "photos"
|
|
for d in (voyage, gpx, photos):
|
|
d.mkdir(parents=True)
|
|
_etape(gpx / "Etape 1.gpx", 55.9, 3.2)
|
|
_etape(gpx / "Etape 2.gpx", 56.7, 2.24)
|
|
_photo(str(photos / "P1.jpg"), "2027:06:11 10:00:00", gps=(56.75, 2.30))
|
|
config.ecrire_toml(str(racine / "site.toml"), {"titre": "Test"})
|
|
config.ecrire_toml(str(voyage / "voyage.toml"), {
|
|
"titre": "Écosse", "sources": {"traces": [str(gpx)], "videos": [], "photos": [str(photos)]}})
|
|
return racine, voyage
|
|
|
|
|
|
def _cfg(racine, voyage):
|
|
return config.reglages_voyage(str(voyage), config.reglages_site(str(racine)))
|
|
|
|
|
|
def test_jour_impose_et_suggestion(voyage_sans_heures):
|
|
racine, voyage = voyage_sans_heures
|
|
a = sources.analyser(str(voyage), _cfg(racine, voyage))
|
|
assert not a.journees and len(a.non_datees) == 2
|
|
medias = sources.positions_medias(str(voyage), a)
|
|
par_nom = {os.path.basename(f): p for f, p in a.non_datees.items()}
|
|
assert sources.suggerer_jour(par_nom["Etape 2.gpx"], medias) == (date(2027, 6, 11), 1)
|
|
assert sources.suggerer_jour(par_nom["Etape 1.gpx"], medias) == (None, 0)
|
|
|
|
config.modifier_toml(str(voyage / "voyage.toml"), {"dates_traces": {"Etape 1.gpx": "2027-06-10"}})
|
|
a = sources.analyser(str(voyage), _cfg(racine, voyage))
|
|
assert sorted(a.journees) == [date(2027, 6, 10)]
|
|
assert [os.path.basename(f) for f in a.non_datees] == ["Etape 2.gpx"]
|
|
# Le titre et les sources ont survécu à la réécriture du fichier
|
|
assert config.lire_toml(str(voyage / "voyage.toml"))["titre"] == "Écosse"
|
|
|
|
|
|
@pytest.fixture
|
|
def client(voyage_sans_heures):
|
|
racine, voyage = voyage_sans_heures
|
|
app = serveur.Application(str(racine))
|
|
srv = ThreadingHTTPServer(("127.0.0.1", 0), None)
|
|
port = srv.server_address[1]
|
|
srv.RequestHandlerClass = serveur.creer_gestionnaire(app, port)
|
|
threading.Thread(target=srv.serve_forever, daemon=True).start()
|
|
|
|
def appel(chemin, donnees=None, htmx=True):
|
|
req = urllib.request.Request(f"http://127.0.0.1:{port}{chemin}",
|
|
data=urllib.parse.urlencode(donnees).encode() if donnees else None)
|
|
if htmx:
|
|
req.add_header("HX-Request", "true")
|
|
try:
|
|
with urllib.request.urlopen(req) as r:
|
|
return r.status, r.read().decode()
|
|
except urllib.error.HTTPError as e:
|
|
return e.code, e.read().decode()
|
|
yield appel, racine, voyage
|
|
srv.shutdown()
|
|
|
|
|
|
def test_serveur(client):
|
|
appel, racine, voyage = client
|
|
code, html = appel("/")
|
|
assert code == 200 and "Écosse" in html
|
|
code, html = appel("/voyage/analyse?v=Ecosse-2027")
|
|
assert "2 traces à rattacher" in html and "11 juin 2027" in html
|
|
# Répartir : un jour par trace dans l'ordre des noms
|
|
code, html = appel("/voyage/dates-suite", {"v": "Ecosse-2027", "debut": "2027-06-10"})
|
|
assert "2 journées" in html
|
|
assert config.lire_toml(str(voyage / "voyage.toml"))["dates_traces"] == {
|
|
"Etape 1.gpx": "2027-06-10", "Etape 2.gpx": "2027-06-11"}
|
|
# Requête de modification sans l'en-tête htmx : refusée
|
|
code, _ = appel("/voyage/date", {"v": "Ecosse-2027", "fichier": "Etape 1.gpx", "jour": ""}, htmx=False)
|
|
assert code == 403
|
|
# Voyage hors du site : refusé
|
|
code, html = appel("/voyage/analyse?v=../../etc")
|
|
assert "introuvable" in html.lower()
|