Web-M1-Ausbau: JSON-Gaesteliste, GA-Verlauf, Python-Serversteuerung, tische.ini-Upload
- bestellung.xml -> bestellung.json: neuer JSONConfig (loest XMLConfig ab), alle work-Beispiele konvertiert, Doku (CLAUDE/README/dateiformate) umgestellt - ga.py: Beobachtungsobjekt Entwicklung zeichnet den Loesungsverlauf auf, neuer Schalter --show-development erzeugt Entwicklung.svg (beste/mittlere/ schlechteste Wertigkeit ueber die Zyklus-Aktionen) - web/main.py: neuer Endpunkt /tische-datei (tische.ini-Upload, ast.literal_eval), Frontend kann jetzt eine tische.ini laden - libs/webserver.py: plattformunabhaengige start/stop/status-Steuerung der Web-Testinstanz (ersetzt run_web_helper.ps1), raeumt auch verwaiste --reload-Prozessbaeume zuverlaessig auf - Sicherheit/Modernisierung: eval() -> ast.literal_eval in Tische/Strafliste, sicherer Mini-Auswerter fuer die String-Ausdruecke in zyklus.cfg, defaultdict(set) in Plaetze - SVG: Tischnummer fett zentriert im Tischkreis statt "Tisch x" darueber - Tests: JSONConfig, Entwicklung und der Zyklus-Auswerter abgedeckt (9 Tests) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,8 @@ import unittest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'libs'))
|
||||
|
||||
from Strukturdaten import Gruppe, Gruppen, Person, Sitzplatzverteilung, Strafliste, Tisch, Tische
|
||||
from Strukturdaten import Gruppe, Gruppen, JSONConfig, Person, Sitzplatzverteilung, Strafliste, Tisch, Tische
|
||||
from ga import Entwicklung, _StringAusdruck
|
||||
|
||||
IMAGES_DIR = os.path.join(os.path.dirname(__file__), '..', 'doc', 'images')
|
||||
|
||||
@@ -169,5 +170,109 @@ class TestSechsTischeKreisDreiergruppen(unittest.TestCase):
|
||||
"Tische %s der Gruppe %s sind keine Nachbarn" % (SV.Gid_Tid[G.Id], G.Id) )
|
||||
|
||||
|
||||
class TestJSONConfig(unittest.TestCase):
|
||||
"""Parsen der bestellung.json: Gruppen, VIP-Bindung, Anzahl-Expansion."""
|
||||
|
||||
def test_gruppen_vips_und_sammelbuchung(self):
|
||||
Daten = {
|
||||
"gruppen": [
|
||||
{
|
||||
"name": "Brautpaar",
|
||||
"tisch": 1,
|
||||
"personen": [
|
||||
{"vorname": "Julia", "nachname": "Brandt"},
|
||||
{"vorname": "Daniel", "nachname": "Sommer", "titel": "Dr."},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Familie Huber",
|
||||
"personen": [
|
||||
{"vorname": "Anna", "nachname": "Huber"},
|
||||
{"vorname": "Berta", "nachname": "Huber"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Musikverein",
|
||||
"anzahl": 5,
|
||||
"personen": [ {"vorname": "Gast", "nachname": "Musikverein"} ],
|
||||
},
|
||||
]
|
||||
}
|
||||
PN, GN, VIPGruppe, VipHash, GruppenNamen = JSONConfig().HandleBestellung(Daten)
|
||||
|
||||
# VIP-Gruppe: beide ans Tisch 1 gebunden, nicht in den GA-Gruppen
|
||||
self.assertEqual( VIPGruppe.Anzahl(), 2 )
|
||||
self.assertEqual( set(VipHash.values()), {1} )
|
||||
self.assertEqual( len(VipHash), 2 )
|
||||
|
||||
# regulaere Gruppen: Familie Huber (2) + Musikverein (5x expandiert) = 7
|
||||
self.assertEqual( GN.NLeute, 7 )
|
||||
self.assertEqual( len(GN.GruppenListe), 2 )
|
||||
|
||||
# Sammelbuchung: die eine Musikverein-Person wurde 5x vervielfacht
|
||||
Groessen = sorted( G.Anzahl() for G in GN.GruppenListe )
|
||||
self.assertEqual( Groessen, [2, 5] )
|
||||
|
||||
# Anzeigenamen kommen durch
|
||||
self.assertIn( "Familie Huber", GruppenNamen.values() )
|
||||
self.assertIn( "Musikverein", GruppenNamen.values() )
|
||||
|
||||
def test_fehlende_pflichtfelder_werfen_fehler(self):
|
||||
with self.assertRaises(ValueError):
|
||||
JSONConfig().HandleBestellung( {"keine_gruppen": []} )
|
||||
with self.assertRaises(ValueError):
|
||||
JSONConfig().HandleBestellung(
|
||||
{"gruppen": [ {"personen": [ {"vorname": "Nur"} ]} ]} )
|
||||
|
||||
|
||||
class TestStringAusdruck(unittest.TestCase):
|
||||
"""Der sichere Zyklus-Ausdruck-Auswerter (Ersatz fuer eval)."""
|
||||
|
||||
def test_verkettung_und_vervielfachung(self):
|
||||
self.assertEqual(
|
||||
_StringAusdruck("'e,'+'s,z,m,j,z,'*3+'s'"),
|
||||
'e,s,z,m,j,z,s,z,m,j,z,s,z,m,j,z,s' )
|
||||
self.assertEqual( _StringAusdruck("'e,s'"), 'e,s' )
|
||||
|
||||
def test_lehnt_code_ab(self):
|
||||
# kein Funktionsaufruf, keine Namen, kein Import
|
||||
for boese in ("__import__('os')", "open('x')", "1+1", "a+'b'"):
|
||||
with self.assertRaises(ValueError):
|
||||
_StringAusdruck(boese)
|
||||
|
||||
|
||||
class TestEntwicklung(unittest.TestCase):
|
||||
"""Das Beobachtungsobjekt zeichnet den Verlauf auf und rendert SVG."""
|
||||
|
||||
class _L:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def test_erfassen_und_svg(self):
|
||||
E = Entwicklung()
|
||||
E.erfassen( 'e', [self._L(-200), self._L(-150), self._L(-90)] )
|
||||
E.erfassen( 's', [self._L(-90), self._L(-70)] )
|
||||
E.erfassen( 'm', [self._L(-70), self._L(-40)] )
|
||||
|
||||
self.assertEqual( len(E.Punkte), 3 )
|
||||
# je Punkt: (index, aktion, best, schnitt, schlecht, N)
|
||||
_i, aktion, best, schnitt, schlecht, n = E.Punkte[0]
|
||||
self.assertEqual( aktion, 'e' )
|
||||
self.assertEqual( best, -90 )
|
||||
self.assertEqual( schlecht, -200 )
|
||||
self.assertEqual( n, 3 )
|
||||
|
||||
Pfad = os.path.join( IMAGES_DIR, 'test_entwicklung.svg' )
|
||||
E.alsSVG( Pfad )
|
||||
with open(Pfad) as f:
|
||||
svg = f.read()
|
||||
self.assertIn( '<svg', svg )
|
||||
self.assertEqual( svg.count('<polyline'), 3 ) # best/schnitt/schlecht
|
||||
|
||||
def test_leere_entwicklung_wirft_beim_svg(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Entwicklung().alsSVG( os.path.join(IMAGES_DIR, 'leer.svg') )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user