Files
Projektarbeit-MYP/backend/tests/test_tapo_integration.py

481 lines
16 KiB
Python

#!/usr/bin/env python3
"""
Umfassende Tests für Tapo-Steckdosen-Integration und Drucker-Verwaltung
Diese Tests stellen sicher, dass:
1. Alle 6 Drucker/Steckdosen immer angezeigt werden
2. Die 3 Status-Zustände korrekt funktionieren: an, aus, nicht erreichbar
3. Das automatische An-/Ausschalten basierend auf Reservierungen funktioniert
4. Die Kalender-Integration für Admins funktioniert
"""
import pytest
import json
import sys
import os
from datetime import datetime, timedelta
from time import sleep
# Pfad zum Backend hinzufügen
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from app import app
from models import init_database, User, Printer, Job, get_db_session
from utils.logging_config import get_logger
logger = get_logger("test_tapo_integration")
# Test-Konfiguration
TEST_USER = {
"username": "testuser",
"email": "test@mercedes-benz.com",
"password": "TestPassword123!",
"name": "Test User"
}
TEST_ADMIN = {
"username": "testadmin",
"email": "admin.test@mercedes-benz.com",
"password": "AdminPassword123!",
"name": "Test Admin"
}
# Die 6 Standard-Drucker mit Tapo-Steckdosen
STANDARD_PRINTERS = [
{
"name": "Drucker 1 - Halle A",
"model": "Prusa MK3S+",
"location": "Halle A - Arbeitsplatz 1",
"ip_address": "192.168.1.101",
"plug_ip": "192.168.1.201",
"plug_username": "admin",
"plug_password": "secure_password"
},
{
"name": "Drucker 2 - Halle A",
"model": "Prusa MK3S+",
"location": "Halle A - Arbeitsplatz 2",
"ip_address": "192.168.1.102",
"plug_ip": "192.168.1.202",
"plug_username": "admin",
"plug_password": "secure_password"
},
{
"name": "Drucker 3 - Halle B",
"model": "Ultimaker S5",
"location": "Halle B - Arbeitsplatz 1",
"ip_address": "192.168.1.103",
"plug_ip": "192.168.1.203",
"plug_username": "admin",
"plug_password": "secure_password"
},
{
"name": "Drucker 4 - Halle B",
"model": "Ultimaker S5",
"location": "Halle B - Arbeitsplatz 2",
"ip_address": "192.168.1.104",
"plug_ip": "192.168.1.204",
"plug_username": "admin",
"plug_password": "secure_password"
},
{
"name": "Drucker 5 - Labor",
"model": "Formlabs Form 3",
"location": "Labor - SLA-Bereich",
"ip_address": "192.168.1.105",
"plug_ip": "192.168.1.205",
"plug_username": "admin",
"plug_password": "secure_password"
},
{
"name": "Drucker 6 - Werkstatt",
"model": "Markforged X7",
"location": "Werkstatt - Spezialbereich",
"ip_address": "192.168.1.106",
"plug_ip": "192.168.1.206",
"plug_username": "admin",
"plug_password": "secure_password"
}
]
class TestTapoIntegration:
"""Test-Suite für Tapo-Steckdosen-Integration"""
@pytest.fixture(autouse=True)
def setup(self):
"""Setup für jeden Test"""
self.app = app
self.app.config['TESTING'] = True
self.app.config['WTF_CSRF_ENABLED'] = False
self.client = self.app.test_client()
with self.app.app_context():
# Datenbank initialisieren
init_database()
# Test-Benutzer erstellen
db_session = get_db_session()
# Normaler Benutzer
self.user = User()
self.user.username = TEST_USER["username"]
self.user.email = TEST_USER["email"]
self.user.set_password(TEST_USER["password"])
self.user.name = TEST_USER["name"]
self.user.role = "user"
db_session.add(self.user)
# Admin-Benutzer
self.admin = User()
self.admin.username = TEST_ADMIN["username"]
self.admin.email = TEST_ADMIN["email"]
self.admin.set_password(TEST_ADMIN["password"])
self.admin.name = TEST_ADMIN["name"]
self.admin.role = "admin"
db_session.add(self.admin)
db_session.commit()
# Standard-Drucker erstellen
self._create_standard_printers()
db_session.close()
yield
# Cleanup nach Test
with self.app.app_context():
db_session = get_db_session()
db_session.query(Job).delete()
db_session.query(Printer).delete()
db_session.query(User).delete()
db_session.commit()
db_session.close()
def _create_standard_printers(self):
"""Erstellt die 6 Standard-Drucker"""
db_session = get_db_session()
for printer_data in STANDARD_PRINTERS:
printer = Printer()
printer.name = printer_data["name"]
printer.model = printer_data["model"]
printer.location = printer_data["location"]
printer.ip_address = printer_data["ip_address"]
printer.plug_ip = printer_data["plug_ip"]
printer.plug_username = printer_data["plug_username"]
printer.plug_password = printer_data["plug_password"]
printer.status = "offline" # Initial-Status
printer.active = True
db_session.add(printer)
db_session.commit()
def _login_as_admin(self):
"""Login als Admin"""
response = self.client.post('/api/login', json={
'username': TEST_ADMIN['username'],
'password': TEST_ADMIN['password']
})
assert response.status_code == 200
return response.get_json()
def _login_as_user(self):
"""Login als normaler Benutzer"""
response = self.client.post('/api/login', json={
'username': TEST_USER['username'],
'password': TEST_USER['password']
})
assert response.status_code == 200
return response.get_json()
def test_all_printers_always_visible(self):
"""Test: Alle 6 Drucker werden immer angezeigt"""
# Als Benutzer einloggen
self._login_as_user()
# Drucker abrufen
response = self.client.get('/api/printers')
assert response.status_code == 200
data = response.get_json()
assert data['success'] is True
assert data['count'] == 6
assert len(data['printers']) == 6
# Prüfen, dass alle Drucker vorhanden sind
printer_names = [p['name'] for p in data['printers']]
for expected_printer in STANDARD_PRINTERS:
assert expected_printer['name'] in printer_names
def test_printer_status_types(self):
"""Test: Die 3 Status-Typen (an, aus, nicht erreichbar)"""
# Als Admin einloggen
self._login_as_admin()
# Status abrufen
response = self.client.get('/api/printers/status')
assert response.status_code == 200
data = response.get_json()
assert data['success'] is True
assert len(data['printers']) == 6
# Prüfen, dass alle Drucker einen gültigen Status haben
for printer in data['printers']:
assert printer['has_plug'] is True
assert printer['plug_ip'] is not None
# Status sollte einer der erwarteten sein
assert printer['plug_status'] in ['on', 'off', 'unreachable', 'error', 'unavailable']
def test_crud_operations_printers(self):
"""Test: CRUD-Operationen für Drucker"""
# Als Admin einloggen
self._login_as_admin()
# CREATE: Neuen Drucker erstellen
new_printer_data = {
"name": "Test-Drucker 7",
"model": "Test Model",
"location": "Test Location",
"ip_address": "192.168.1.107",
"plug_ip": "192.168.1.207",
"plug_username": "test",
"plug_password": "test123"
}
response = self.client.post('/api/admin/printers', json=new_printer_data)
assert response.status_code == 201
data = response.get_json()
assert data['success'] is True
printer_id = data['printer']['id']
# READ: Einzelnen Drucker abrufen
response = self.client.get(f'/api/admin/printers/{printer_id}')
assert response.status_code == 200
data = response.get_json()
assert data['printer']['name'] == new_printer_data['name']
# UPDATE: Drucker aktualisieren
update_data = {
"name": "Test-Drucker 7 Updated",
"location": "Updated Location"
}
response = self.client.put(f'/api/admin/printers/{printer_id}', json=update_data)
assert response.status_code == 200
data = response.get_json()
assert data['printer']['name'] == update_data['name']
assert data['printer']['location'] == update_data['location']
# DELETE: Drucker löschen
response = self.client.delete(f'/api/admin/printers/{printer_id}')
assert response.status_code == 200
# Verifizieren, dass gelöscht wurde
response = self.client.get(f'/api/admin/printers/{printer_id}')
assert response.status_code == 404
def test_automatic_plug_control_with_jobs(self):
"""Test: Automatisches An-/Ausschalten der Steckdosen basierend auf Jobs"""
# Als Benutzer einloggen
self._login_as_user()
# Job für in 5 Minuten erstellen
start_time = datetime.now() + timedelta(minutes=5)
end_time = start_time + timedelta(hours=2)
job_data = {
"name": "Test-Druckauftrag",
"description": "Automatischer Steuerungstest",
"printer_id": 1,
"start_at": start_time.isoformat(),
"end_at": end_time.isoformat(),
"duration_minutes": 120
}
response = self.client.post('/api/jobs', json=job_data)
assert response.status_code in [200, 201]
job_id = response.get_json()['id']
# Job-Details abrufen
response = self.client.get(f'/api/jobs/{job_id}')
assert response.status_code == 200
job = response.get_json()
assert job['status'] == 'scheduled'
# Simulieren: Zeit vergeht, Job sollte starten
# (In der Realität würde der Scheduler dies automatisch tun)
# Job manuell starten (simuliert Scheduler)
response = self.client.post(f'/api/jobs/{job_id}/start')
assert response.status_code == 200
# Prüfen, dass Job läuft
response = self.client.get(f'/api/jobs/{job_id}')
assert response.status_code == 200
job = response.get_json()
assert job['status'] == 'running'
def test_calendar_shows_printer_status_for_admin(self):
"""Test: Kalender zeigt Drucker-Status für Admin"""
# Als Admin einloggen
self._login_as_admin()
# Jobs für verschiedene Zeiträume erstellen
now = datetime.now()
# Job 1: Läuft gerade
running_job = {
"name": "Laufender Druck",
"printer_id": 1,
"start_at": (now - timedelta(hours=1)).isoformat(),
"end_at": (now + timedelta(hours=1)).isoformat(),
"duration_minutes": 120,
"status": "running"
}
# Job 2: Geplant für später
scheduled_job = {
"name": "Geplanter Druck",
"printer_id": 2,
"start_at": (now + timedelta(hours=2)).isoformat(),
"end_at": (now + timedelta(hours=4)).isoformat(),
"duration_minutes": 120,
"status": "scheduled"
}
# Jobs erstellen
for job_data in [running_job, scheduled_job]:
response = self.client.post('/api/jobs', json=job_data)
assert response.status_code in [200, 201]
# Kalender-Events abrufen
response = self.client.get('/api/calendar/events')
assert response.status_code == 200
events = response.get_json()
assert len(events) >= 2
# Prüfen, dass Events Drucker-Status enthalten
for event in events:
if 'extendedProps' in event:
assert 'printer_id' in event['extendedProps']
assert 'status' in event['extendedProps']
def test_printer_status_persistence(self):
"""Test: Drucker-Status wird korrekt gespeichert und abgerufen"""
# Als Admin einloggen
self._login_as_admin()
# Status für alle Drucker abrufen
response = self.client.get('/api/printers/status')
assert response.status_code == 200
initial_status = response.get_json()
# Manuell einen Drucker "einschalten" (simuliert)
response = self.client.post('/api/tapo/control', json={
'printer_id': 1,
'action': 'on'
})
# Kann fehlschlagen wenn Steckdose nicht erreichbar
# assert response.status_code == 200
# Status erneut abrufen
response = self.client.get('/api/printers/status')
assert response.status_code == 200
updated_status = response.get_json()
# Mindestens die Anzahl sollte gleich bleiben
assert len(updated_status['printers']) == len(initial_status['printers'])
def test_error_handling_unreachable_plugs(self):
"""Test: Fehlerbehandlung für nicht erreichbare Steckdosen"""
# Als Admin einloggen
self._login_as_admin()
# Versuchen, eine nicht erreichbare Steckdose zu steuern
response = self.client.post('/api/tapo/control', json={
'printer_id': 1,
'action': 'on'
})
# Sollte entweder erfolgreich sein oder einen kontrollierten Fehler zurückgeben
assert response.status_code in [200, 400, 500]
if response.status_code != 200:
data = response.get_json()
assert 'error' in data or 'message' in data
def test_concurrent_job_scheduling(self):
"""Test: Mehrere Jobs gleichzeitig planen"""
# Als Benutzer einloggen
self._login_as_user()
# Mehrere Jobs für verschiedene Drucker erstellen
start_time = datetime.now() + timedelta(hours=1)
jobs = []
for i in range(3):
job_data = {
"name": f"Concurrent Job {i+1}",
"printer_id": i+1,
"start_at": start_time.isoformat(),
"end_at": (start_time + timedelta(hours=2)).isoformat(),
"duration_minutes": 120
}
response = self.client.post('/api/jobs', json=job_data)
assert response.status_code in [200, 201]
jobs.append(response.get_json())
# Prüfen, dass alle Jobs erstellt wurden
assert len(jobs) == 3
# Alle Jobs abrufen
response = self.client.get('/api/jobs')
assert response.status_code == 200
all_jobs = response.get_json()
assert len(all_jobs) >= 3
def test_admin_dashboard_printer_overview(self):
"""Test: Admin-Dashboard zeigt Drucker-Übersicht"""
# Als Admin einloggen
self._login_as_admin()
# Dashboard-Daten abrufen
response = self.client.get('/api/admin/dashboard')
if response.status_code == 404:
# Alternative: Stats abrufen
response = self.client.get('/api/stats')
if response.status_code == 200:
data = response.get_json()
# Prüfen auf relevante Daten
assert isinstance(data, dict)
def run_tests():
"""Führt die Tests aus"""
logger.info("Starte Tapo-Integrationstests...")
# Pytest ausführen
pytest_args = [
__file__,
'-v', # Verbose
'-s', # Keine Capture, zeige print-Ausgaben
'--tb=short' # Kurze Traceback-Ausgabe
]
result = pytest.main(pytest_args)
if result == 0:
logger.info("✅ Alle Tests erfolgreich bestanden!")
else:
logger.error("❌ Tests fehlgeschlagen!")
return result
if __name__ == "__main__":
sys.exit(run_tests())