added tests
This commit is contained in:
parent
4e359cf3ef
commit
cbe9369712
9 changed files with 960 additions and 150 deletions
550
backend/test_automated.py
Normal file
550
backend/test_automated.py
Normal 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)
|
Loading…
Add table
Add a link
Reference in a new issue