added tests

This commit is contained in:
Jan Felix Wiebe 2025-07-09 22:18:21 +02:00
parent 4e359cf3ef
commit cbe9369712
9 changed files with 960 additions and 150 deletions

550
backend/test_automated.py Normal file
View file

@ -0,0 +1,550 @@
#!/usr/bin/env python3
"""
Automatisierte Tests für die Tschunk Order API
Verwendet pytest und FastAPI TestClient
"""
import pytest
import asyncio
import json
from httpx import AsyncClient
from fastapi.testclient import TestClient
from main import app
from models import DrinkType, MateType
class TestTschunkOrderAPI:
"""Test-Klasse für die Tschunk Order API"""
@pytest.fixture
def client(self):
"""TestClient für HTTP-Endpunkte"""
return TestClient(app)
@pytest.fixture
def sample_order(self):
"""Beispiel-Bestellung für Tests"""
return {
"drinks": [
{
"drink_type": "Tschunk",
"mate_type": "Club Mate",
"quantity": 2,
"notes": "Test-Bestellung"
},
{
"drink_type": "Virgin Tschunk",
"mate_type": "Flora Mate",
"quantity": 1
}
]
}
def test_root_endpoint(self, client):
"""Test des Root-Endpunkts"""
response = client.get("/")
assert response.status_code == 200
data = response.json()
assert data["message"] == "Willkommen zur Tschunk Order API!"
assert data["version"] == "1.0.0"
assert "POST /orders" in data["endpoints"]
assert "WS /ws" in data["endpoints"]
# Prüfe verfügbare Getränke
assert "Tschunk" in data["available_drinks"]
assert "Virgin Tschunk" in data["available_drinks"]
assert "Flora Mate" in data["available_mate_types"]
assert "Club Mate" in data["available_mate_types"]
def test_drinks_endpoint(self, client):
"""Test des Drinks-Endpunkts"""
response = client.get("/drinks")
assert response.status_code == 200
data = response.json()
assert "drink_types" in data
assert "mate_types" in data
# Prüfe alle Getränketypen
expected_drinks = [
"Tschunk",
"Tschunk Hannover-Mische",
"Tschunk alkoholfreier Rum",
"Virgin Tschunk"
]
for drink in expected_drinks:
assert drink in data["drink_types"]
# Prüfe Mate-Sorten
expected_mates = ["Flora Mate", "Club Mate"]
for mate in expected_mates:
assert mate in data["mate_types"]
def test_create_order_success(self, client, sample_order):
"""Test erfolgreiche Bestellerstellung"""
response = client.post("/orders", json=sample_order)
assert response.status_code == 201
order = response.json()
assert "id" in order
assert "order_date" in order
assert "drinks" in order
assert len(order["drinks"]) == 2
# Prüfe erste Bestellung
first_drink = order["drinks"][0]
assert first_drink["drink_type"] == "Tschunk"
assert first_drink["mate_type"] == "Club Mate"
assert first_drink["quantity"] == 2
assert first_drink["notes"] == "Test-Bestellung"
# Prüfe zweite Bestellung (ohne notes)
second_drink = order["drinks"][1]
assert second_drink["drink_type"] == "Virgin Tschunk"
assert second_drink["mate_type"] == "Flora Mate"
assert second_drink["quantity"] == 1
assert second_drink["notes"] is None
def test_create_order_empty_drinks(self, client):
"""Test Bestellerstellung ohne Getränke"""
response = client.post("/orders", json={"drinks": []})
assert response.status_code == 400
assert "Mindestens ein Getränk muss bestellt werden" in response.json()["detail"]
def test_create_order_invalid_drink_type(self, client):
"""Test Bestellerstellung mit ungültigem Getränketyp"""
invalid_order = {
"drinks": [
{
"drink_type": "Ungültiges Getränk",
"mate_type": "Club Mate"
}
]
}
response = client.post("/orders", json=invalid_order)
assert response.status_code == 422 # Validation Error
def test_get_all_orders(self, client, sample_order):
"""Test Abrufen aller Bestellungen"""
# Erstelle zuerst eine Bestellung
create_response = client.post("/orders", json=sample_order)
assert create_response.status_code == 201
# Hole alle Bestellungen
response = client.get("/orders")
assert response.status_code == 200
orders = response.json()
assert isinstance(orders, list)
assert len(orders) >= 1
# Prüfe, dass unsere Bestellung dabei ist
order_ids = [order["id"] for order in orders]
assert create_response.json()["id"] in order_ids
def test_delete_order_success(self, client, sample_order):
"""Test erfolgreiche Bestelllöschung"""
# Erstelle zuerst eine Bestellung
create_response = client.post("/orders", json=sample_order)
assert create_response.status_code == 201
order_id = create_response.json()["id"]
# Lösche die Bestellung
response = client.delete(f"/orders/{order_id}")
assert response.status_code == 200
assert f"Bestellung {order_id} wurde erfolgreich gelöscht" in response.json()["message"]
# Prüfe, dass die Bestellung wirklich gelöscht wurde
get_response = client.get("/orders")
orders = get_response.json()
order_ids = [order["id"] for order in orders]
assert order_id not in order_ids
def test_delete_order_not_found(self, client):
"""Test Löschung einer nicht existierenden Bestellung"""
response = client.delete("/orders/non-existent-id")
assert response.status_code == 404
assert "Bestellung nicht gefunden" in response.json()["detail"]
def test_order_with_default_quantity(self, client):
"""Test Bestellung ohne quantity (sollte 1 sein)"""
order_without_quantity = {
"drinks": [
{
"drink_type": "Tschunk",
"mate_type": "Club Mate"
# quantity wird nicht angegeben
}
]
}
response = client.post("/orders", json=order_without_quantity)
assert response.status_code == 201
order = response.json()
assert order["drinks"][0]["quantity"] == 1
def test_order_with_notes(self, client):
"""Test Bestellung mit Anmerkungen"""
order_with_notes = {
"drinks": [
{
"drink_type": "Virgin Tschunk",
"mate_type": "Flora Mate",
"quantity": 1,
"notes": "Bitte extra kalt servieren"
}
]
}
response = client.post("/orders", json=order_with_notes)
assert response.status_code == 201
order = response.json()
assert order["drinks"][0]["notes"] == "Bitte extra kalt servieren"
class TestWebSocket:
"""Test-Klasse für WebSocket-Funktionalität"""
@pytest.mark.asyncio
async def test_websocket_connection(self):
"""Test WebSocket-Verbindung und initiale Nachricht"""
from websocket_manager import websocket_manager
# Erstelle einen Test-WebSocket
class MockWebSocket:
def __init__(self):
self.messages = []
self.closed = False
async def accept(self):
pass
async def send_text(self, message):
self.messages.append(message)
async def receive_text(self):
return "ping"
async def close(self):
self.closed = True
# Teste WebSocket-Manager
mock_ws = MockWebSocket()
# Verbinde
await websocket_manager.connect(mock_ws)
assert len(websocket_manager.active_connections) == 1
# Teste Broadcast
test_message = {"type": "test", "data": "test"}
await websocket_manager.broadcast(test_message)
assert len(mock_ws.messages) == 1
# Teste spezifische Broadcasts
await websocket_manager.broadcast_order_created({"id": "test", "drinks": []})
assert len(mock_ws.messages) == 2
await websocket_manager.broadcast_order_deleted("test-id")
assert len(mock_ws.messages) == 3
# Trenne Verbindung
websocket_manager.disconnect(mock_ws)
assert len(websocket_manager.active_connections) == 0
@pytest.mark.asyncio
async def test_websocket_integration_create_order(self):
"""Test WebSocket-Broadcast bei Bestellerstellung über API"""
from websocket_manager import websocket_manager
from database import OrderDatabase
from models import Drink, DrinkType, MateType
# Erstelle einen Test-WebSocket
class MockWebSocket:
def __init__(self):
self.messages = []
self.closed = False
async def accept(self):
pass
async def send_text(self, message):
self.messages.append(message)
async def receive_text(self):
return "ping"
async def close(self):
self.closed = True
# Verbinde Mock-WebSocket
mock_ws = MockWebSocket()
await websocket_manager.connect(mock_ws)
# Erstelle Test-Datenbank
test_db = OrderDatabase()
# Erstelle Bestellung über Datenbank (sollte WebSocket-Broadcast auslösen)
drinks = [
Drink(
drink_type=DrinkType.TSCHUNK,
mate_type=MateType.CLUB_MATE,
quantity=2,
notes="Test-Bestellung"
)
]
order = await test_db.create_order(drinks)
# Prüfe, dass WebSocket-Nachricht gesendet wurde
assert len(mock_ws.messages) >= 1
# Finde die "order_created" Nachricht
order_created_message = None
for message in mock_ws.messages:
try:
data = json.loads(message)
if data.get("type") == "order_created":
order_created_message = data
break
except json.JSONDecodeError:
continue
assert order_created_message is not None
assert order_created_message["type"] == "order_created"
assert "order" in order_created_message
assert order_created_message["order"]["id"] == order.id
# Cleanup
websocket_manager.disconnect(mock_ws)
@pytest.mark.asyncio
async def test_websocket_integration_delete_order(self):
"""Test WebSocket-Broadcast bei Bestelllöschung über API"""
from websocket_manager import websocket_manager
from database import OrderDatabase
from models import Drink, DrinkType, MateType
# Erstelle einen Test-WebSocket
class MockWebSocket:
def __init__(self):
self.messages = []
self.closed = False
async def accept(self):
pass
async def send_text(self, message):
self.messages.append(message)
async def receive_text(self):
return "ping"
async def close(self):
self.closed = True
# Verbinde Mock-WebSocket
mock_ws = MockWebSocket()
await websocket_manager.connect(mock_ws)
# Erstelle Test-Datenbank
test_db = OrderDatabase()
# Erstelle zuerst eine Bestellung
drinks = [
Drink(
drink_type=DrinkType.VIRGIN_TSCHUNK,
mate_type=MateType.FLORA_MATE,
quantity=1
)
]
order = await test_db.create_order(drinks)
order_id = order.id
# Lösche die Bestellung (sollte WebSocket-Broadcast auslösen)
success = await test_db.delete_order(order_id)
assert success is True
# Prüfe, dass WebSocket-Nachricht gesendet wurde
assert len(mock_ws.messages) >= 2 # Mindestens create + delete
# Finde die "order_deleted" Nachricht
order_deleted_message = None
for message in mock_ws.messages:
try:
data = json.loads(message)
if data.get("type") == "order_deleted":
order_deleted_message = data
break
except json.JSONDecodeError:
continue
assert order_deleted_message is not None
assert order_deleted_message["type"] == "order_deleted"
assert "order_id" in order_deleted_message
assert order_deleted_message["order_id"] == order_id
# Cleanup
websocket_manager.disconnect(mock_ws)
@pytest.mark.asyncio
async def test_websocket_multiple_clients(self):
"""Test WebSocket-Broadcast an mehrere verbundene Clients"""
from websocket_manager import websocket_manager
from database import OrderDatabase
from models import Drink, DrinkType, MateType
# Erstelle mehrere Test-WebSockets
class MockWebSocket:
def __init__(self, client_id):
self.client_id = client_id
self.messages = []
self.closed = False
async def accept(self):
pass
async def send_text(self, message):
self.messages.append(message)
async def receive_text(self):
return "ping"
async def close(self):
self.closed = True
# Verbinde mehrere Mock-WebSockets
mock_ws1 = MockWebSocket("client1")
mock_ws2 = MockWebSocket("client2")
mock_ws3 = MockWebSocket("client3")
await websocket_manager.connect(mock_ws1)
await websocket_manager.connect(mock_ws2)
await websocket_manager.connect(mock_ws3)
assert len(websocket_manager.active_connections) == 3
# Erstelle Test-Datenbank
test_db = OrderDatabase()
# Erstelle Bestellung (sollte an alle Clients broadcasten)
drinks = [
Drink(
drink_type=DrinkType.TSCHUNK_HANNOVER_MISCHE,
mate_type=MateType.CLUB_MATE,
quantity=3,
notes="Multi-Client Test"
)
]
order = await test_db.create_order(drinks)
# Prüfe, dass alle Clients die Nachricht erhalten haben
for mock_ws in [mock_ws1, mock_ws2, mock_ws3]:
assert len(mock_ws.messages) >= 1
# Finde die "order_created" Nachricht
order_created_found = False
for message in mock_ws.messages:
try:
data = json.loads(message)
if data.get("type") == "order_created":
order_created_found = True
assert data["order"]["id"] == order.id
break
except json.JSONDecodeError:
continue
assert order_created_found, f"Client {mock_ws.client_id} hat keine order_created Nachricht erhalten"
# Cleanup
websocket_manager.disconnect(mock_ws1)
websocket_manager.disconnect(mock_ws2)
websocket_manager.disconnect(mock_ws3)
class TestDatabase:
"""Test-Klasse für Datenbankfunktionalität"""
@pytest.mark.asyncio
async def test_database_operations(self):
"""Test Datenbankoperationen"""
from database import OrderDatabase
from models import Drink, DrinkType, MateType
# Erstelle neue Datenbank-Instanz für Tests
test_db = OrderDatabase()
# Teste Bestellerstellung
drinks = [
Drink(
drink_type=DrinkType.TSCHUNK,
mate_type=MateType.CLUB_MATE,
quantity=2,
notes="Test"
)
]
order = await test_db.create_order(drinks)
assert order.id is not None
assert len(order.drinks) == 1
assert order.drinks[0].drink_type == DrinkType.TSCHUNK
# Teste Abrufen aller Bestellungen
all_orders = test_db.get_all_orders()
assert len(all_orders) == 1
assert all_orders[0].id == order.id
# Teste Abrufen spezifischer Bestellung
retrieved_order = test_db.get_order_by_id(order.id)
assert retrieved_order is not None
assert retrieved_order.id == order.id
# Teste Löschung
success = await test_db.delete_order(order.id)
assert success is True
# Prüfe, dass Bestellung gelöscht wurde
all_orders_after = test_db.get_all_orders()
assert len(all_orders_after) == 0
# Teste Löschung nicht existierender Bestellung
success = await test_db.delete_order("non-existent")
assert success is False
def run_all_tests():
"""Führe alle Tests aus"""
import sys
import subprocess
print("🧪 Starte automatisierte Tests...")
print("=" * 50)
# Führe pytest aus
result = subprocess.run([
sys.executable, "-m", "pytest",
"test_automated.py",
"-v",
"--tb=short"
], capture_output=True, text=True)
print(result.stdout)
if result.stderr:
print("STDERR:", result.stderr)
print("=" * 50)
if result.returncode == 0:
print("✅ Alle Tests erfolgreich!")
else:
print("❌ Einige Tests fehlgeschlagen!")
return result.returncode == 0
if __name__ == "__main__":
success = run_all_tests()
exit(0 if success else 1)