#!/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)