550 lines
No EOL
18 KiB
Python
550 lines
No EOL
18 KiB
Python
#!/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) |