Wichtel-Werkstatt: Secret-Santa-App mit Räumen, Ausschlüssen und Recovery-Links
- FastAPI + SQLite, serverseitig gerenderte Templates, mobil-zuerst - Admin-Passwort, einseitige Ausschlüsse, eingefrorene Auslosung - Teilnehmer sehen Ergebnis per Cookie; Einmal-Recovery-Links bei Verlust - Docker/podman-tauglich (Entrypoint mit Privilegien-Drop, SELinux-:z) - Unit-Tests für Auslosung, E2E-Testskript (30 Checks)
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""Tests für die Auslosungslogik.
|
||||
|
||||
Laufen ohne Abhängigkeiten: python tests/test_draw.py
|
||||
(sind aber auch pytest-kompatibel)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from app.draw import draw_assignment
|
||||
|
||||
|
||||
def check_mapping(mapping, ids, exclusions):
|
||||
assert sorted(mapping.keys()) == sorted(ids), "jeder muss genau einmal geben"
|
||||
assert sorted(mapping.values()) == sorted(ids), "jeder muss genau einmal gezogen werden"
|
||||
for giver, receiver in mapping.items():
|
||||
assert giver != receiver, "niemand darf sich selbst ziehen"
|
||||
assert (giver, receiver) not in exclusions, "Ausschlüsse müssen gelten"
|
||||
|
||||
|
||||
def test_basically():
|
||||
ids = ["a", "b", "c", "d"]
|
||||
mapping = draw_assignment(ids, set())
|
||||
assert mapping is not None
|
||||
check_mapping(mapping, ids, set())
|
||||
|
||||
|
||||
def test_mindestgroesse():
|
||||
assert draw_assignment([], set()) is None
|
||||
assert draw_assignment(["a"], set()) is None
|
||||
|
||||
|
||||
def test_ausschluesse_werden_respektiert():
|
||||
ids = ["anna", "ben", "clara", "david"]
|
||||
# Ehepaar: Anna und Ben dürfen sich nicht gegenseitig ziehen.
|
||||
exclusions = {("anna", "ben"), ("ben", "anna")}
|
||||
rng = random.Random(42)
|
||||
for _ in range(300):
|
||||
mapping = draw_assignment(ids, exclusions, rng=rng)
|
||||
assert mapping is not None
|
||||
check_mapping(mapping, ids, exclusions)
|
||||
|
||||
|
||||
def test_unloesbar_zwei_personen_mit_ausschluss():
|
||||
ids = ["a", "b"]
|
||||
assert draw_assignment(ids, {("a", "b")}) is None
|
||||
|
||||
|
||||
def test_unloesbar_kein_moeglicher_empfaenger():
|
||||
# a darf weder b noch c ziehen → keine gültige Auslosung möglich.
|
||||
ids = ["a", "b", "c"]
|
||||
exclusions = {("a", "b"), ("a", "c")}
|
||||
assert draw_assignment(ids, exclusions) is None
|
||||
|
||||
|
||||
def test_zufall_variiert():
|
||||
ids = [f"p{i}" for i in range(8)]
|
||||
exclusions = {("p0", "p1"), ("p2", "p3")}
|
||||
rng = random.Random(7)
|
||||
results = set()
|
||||
for _ in range(100):
|
||||
mapping = draw_assignment(ids, exclusions, rng=rng)
|
||||
assert mapping is not None
|
||||
check_mapping(mapping, ids, exclusions)
|
||||
results.add(tuple(sorted(mapping.items())))
|
||||
assert len(results) > 1, "Auslosung soll zufällig variieren"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
failures = 0
|
||||
for name, fn in sorted(globals().items()):
|
||||
if name.startswith("test_") and callable(fn):
|
||||
try:
|
||||
fn()
|
||||
print(f" ✓ {name}")
|
||||
except AssertionError as e:
|
||||
failures += 1
|
||||
print(f" ✗ {name}: {e}")
|
||||
print("ALLE TESTS OK" if failures == 0 else f"{failures} TEST(S) FEHLGESCHLAGEN")
|
||||
sys.exit(1 if failures else 0)
|
||||
Reference in New Issue
Block a user