"Update backend configuration and tests for improved stability"
This commit is contained in:
parent
c1e8ee01c5
commit
a11721f677
@ -90,6 +90,18 @@ def create_app(config_name=None):
|
|||||||
# Hintergrund-Tasks registrieren
|
# Hintergrund-Tasks registrieren
|
||||||
register_background_tasks(app)
|
register_background_tasks(app)
|
||||||
|
|
||||||
|
# Parse PRINTERS als JSON
|
||||||
|
printers_env = app.config.get('PRINTERS', '{}')
|
||||||
|
if isinstance(printers_env, str):
|
||||||
|
try:
|
||||||
|
app.config['PRINTERS'] = json.loads(printers_env)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
app.config['PRINTERS'] = {}
|
||||||
|
elif isinstance(printers_env, dict):
|
||||||
|
app.config['PRINTERS'] = printers_env
|
||||||
|
else:
|
||||||
|
app.config['PRINTERS'] = {}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
# Initialisierung - wird später durch create_app ersetzt
|
# Initialisierung - wird später durch create_app ersetzt
|
||||||
@ -1943,4 +1955,16 @@ else:
|
|||||||
printers_config = json.loads(app.config.get('PRINTERS', '{}'))
|
printers_config = json.loads(app.config.get('PRINTERS', '{}'))
|
||||||
if printers_config:
|
if printers_config:
|
||||||
init_printers()
|
init_printers()
|
||||||
setup_frontend_v2()
|
setup_frontend_v2()
|
||||||
|
|
||||||
|
# Parse PRINTERS als JSON
|
||||||
|
printers_env = app.config.get('PRINTERS', '{}')
|
||||||
|
if isinstance(printers_env, str):
|
||||||
|
try:
|
||||||
|
app.config['PRINTERS'] = json.loads(printers_env)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
app.config['PRINTERS'] = {}
|
||||||
|
elif isinstance(printers_env, dict):
|
||||||
|
app.config['PRINTERS'] = printers_env
|
||||||
|
else:
|
||||||
|
app.config['PRINTERS'] = {}
|
@ -33,6 +33,19 @@ class Config:
|
|||||||
# Drucker-Konfiguration
|
# Drucker-Konfiguration
|
||||||
PRINTERS = os.environ.get('PRINTERS', '{}')
|
PRINTERS = os.environ.get('PRINTERS', '{}')
|
||||||
|
|
||||||
|
# JSON-Konfiguration parsen
|
||||||
|
@property
|
||||||
|
def PRINTERS_DICT(self):
|
||||||
|
"""Parse PRINTERS configuration as JSON"""
|
||||||
|
import json
|
||||||
|
printers_str = self.PRINTERS
|
||||||
|
if isinstance(printers_str, dict):
|
||||||
|
return printers_str
|
||||||
|
try:
|
||||||
|
return json.loads(printers_str) if printers_str else {}
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
return {}
|
||||||
|
|
||||||
# API-Konfiguration
|
# API-Konfiguration
|
||||||
API_KEY = os.environ.get('API_KEY')
|
API_KEY = os.environ.get('API_KEY')
|
||||||
|
|
||||||
@ -112,35 +125,64 @@ class ProductionConfig(Config):
|
|||||||
if not os.path.exists('logs'):
|
if not os.path.exists('logs'):
|
||||||
os.mkdir('logs')
|
os.mkdir('logs')
|
||||||
|
|
||||||
file_handler = RotatingFileHandler(
|
# Windows-kompatibles Logging
|
||||||
'logs/myp.log',
|
import platform
|
||||||
maxBytes=Config.LOG_MAX_BYTES,
|
if platform.system() == 'Windows':
|
||||||
backupCount=Config.LOG_BACKUP_COUNT
|
# Windows: Verwende TimedRotatingFileHandler statt RotatingFileHandler
|
||||||
)
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
|
|
||||||
|
file_handler = TimedRotatingFileHandler(
|
||||||
|
'logs/myp.log',
|
||||||
|
when='midnight',
|
||||||
|
interval=1,
|
||||||
|
backupCount=Config.LOG_BACKUP_COUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
error_handler = TimedRotatingFileHandler(
|
||||||
|
'logs/myp-errors.log',
|
||||||
|
when='midnight',
|
||||||
|
interval=1,
|
||||||
|
backupCount=Config.LOG_BACKUP_COUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
security_handler = TimedRotatingFileHandler(
|
||||||
|
'logs/security.log',
|
||||||
|
when='midnight',
|
||||||
|
interval=1,
|
||||||
|
backupCount=Config.LOG_BACKUP_COUNT
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Linux/Unix: Verwende RotatingFileHandler
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
'logs/myp.log',
|
||||||
|
maxBytes=Config.LOG_MAX_BYTES,
|
||||||
|
backupCount=Config.LOG_BACKUP_COUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
error_handler = RotatingFileHandler(
|
||||||
|
'logs/myp-errors.log',
|
||||||
|
maxBytes=Config.LOG_MAX_BYTES,
|
||||||
|
backupCount=Config.LOG_BACKUP_COUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
security_handler = RotatingFileHandler(
|
||||||
|
'logs/security.log',
|
||||||
|
maxBytes=Config.LOG_MAX_BYTES,
|
||||||
|
backupCount=Config.LOG_BACKUP_COUNT
|
||||||
|
)
|
||||||
|
|
||||||
file_handler.setFormatter(logging.Formatter(
|
file_handler.setFormatter(logging.Formatter(
|
||||||
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
||||||
))
|
))
|
||||||
file_handler.setLevel(logging.INFO)
|
file_handler.setLevel(logging.INFO)
|
||||||
app.logger.addHandler(file_handler)
|
app.logger.addHandler(file_handler)
|
||||||
|
|
||||||
# Error-Logging
|
|
||||||
error_handler = RotatingFileHandler(
|
|
||||||
'logs/myp-errors.log',
|
|
||||||
maxBytes=Config.LOG_MAX_BYTES,
|
|
||||||
backupCount=Config.LOG_BACKUP_COUNT
|
|
||||||
)
|
|
||||||
error_handler.setFormatter(logging.Formatter(
|
error_handler.setFormatter(logging.Formatter(
|
||||||
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
||||||
))
|
))
|
||||||
error_handler.setLevel(logging.ERROR)
|
error_handler.setLevel(logging.ERROR)
|
||||||
app.logger.addHandler(error_handler)
|
app.logger.addHandler(error_handler)
|
||||||
|
|
||||||
# Security-Logging
|
|
||||||
security_handler = RotatingFileHandler(
|
|
||||||
'logs/security.log',
|
|
||||||
maxBytes=Config.LOG_MAX_BYTES,
|
|
||||||
backupCount=Config.LOG_BACKUP_COUNT
|
|
||||||
)
|
|
||||||
security_handler.setFormatter(logging.Formatter(
|
security_handler.setFormatter(logging.Formatter(
|
||||||
'%(asctime)s SECURITY %(levelname)s: %(message)s [%(name)s]'
|
'%(asctime)s SECURITY %(levelname)s: %(message)s [%(name)s]'
|
||||||
))
|
))
|
||||||
@ -154,10 +196,13 @@ class ProductionConfig(Config):
|
|||||||
app.logger.setLevel(logging.INFO)
|
app.logger.setLevel(logging.INFO)
|
||||||
app.logger.info('MYP Backend starting in production mode')
|
app.logger.info('MYP Backend starting in production mode')
|
||||||
|
|
||||||
# Sicherheits-Middleware registrieren
|
# Sicherheits-Middleware registrieren (optional)
|
||||||
if app.config.get('SECURITY_ENABLED', True):
|
if app.config.get('SECURITY_ENABLED', True):
|
||||||
from security import security_middleware
|
try:
|
||||||
security_middleware.init_app(app)
|
from security import security_middleware
|
||||||
|
security_middleware.init_app(app)
|
||||||
|
except ImportError:
|
||||||
|
app.logger.warning('Security module not found, skipping security middleware')
|
||||||
|
|
||||||
class TestingConfig(Config):
|
class TestingConfig(Config):
|
||||||
"""Konfiguration für die Testumgebung."""
|
"""Konfiguration für die Testumgebung."""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# 🏭 MYP Backend - Standalone Server Konfiguration
|
# MYP Backend - Standalone Server Konfiguration
|
||||||
# Umgebungsvariablen ausschließlich für den Backend-Server
|
# Umgebungsvariablen ausschließlich für den Backend-Server
|
||||||
|
|
||||||
# === FLASK KONFIGURATION ===
|
# === FLASK KONFIGURATION ===
|
||||||
|
@ -1,31 +1,54 @@
|
|||||||
# Core Flask-Abhängigkeiten
|
# Core Flask framework
|
||||||
flask==2.3.3
|
Flask==3.0.3
|
||||||
flask-cors==4.0.0
|
Werkzeug==3.0.3
|
||||||
werkzeug==2.3.7
|
|
||||||
|
|
||||||
# Authentifizierung und Sicherheit
|
# Flask extensions
|
||||||
pyjwt==2.8.0
|
Flask-CORS==4.0.0
|
||||||
flask-wtf==1.1.1
|
|
||||||
|
# Security
|
||||||
flask-talisman==1.1.0
|
flask-talisman==1.1.0
|
||||||
|
|
||||||
# Umgebung und Konfiguration
|
# HTTP client
|
||||||
python-dotenv==1.0.0
|
|
||||||
|
|
||||||
# WSGI-Server für Produktion
|
|
||||||
gunicorn==21.2.0
|
|
||||||
waitress==2.1.2
|
|
||||||
|
|
||||||
# Netzwerk und Hardware-Integration
|
|
||||||
PyP100==0.0.19
|
|
||||||
netifaces==0.11.0
|
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
|
|
||||||
# Monitoring und Logging
|
# Networking and socket handling
|
||||||
flask-healthcheck==0.1.0
|
python-socketio==5.11.4
|
||||||
prometheus-flask-exporter==0.23.0
|
|
||||||
psutil==5.9.6
|
|
||||||
|
|
||||||
# Entwicklung und Testing (optional)
|
# Threading utilities
|
||||||
|
eventlet==0.36.1
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
|
||||||
|
# Cryptography and hashing
|
||||||
|
PyJWT==2.8.0
|
||||||
|
cryptography==41.0.7
|
||||||
|
|
||||||
|
# Date and time handling
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
|
||||||
|
# JSON Web Tokens
|
||||||
|
flask-jwt-extended==4.6.0
|
||||||
|
|
||||||
|
# Database
|
||||||
|
SQLAlchemy==2.0.23
|
||||||
|
Flask-Migrate==4.0.5
|
||||||
|
|
||||||
|
# Production server
|
||||||
|
gunicorn==21.2.0
|
||||||
|
|
||||||
|
# Monitoring
|
||||||
|
prometheus_client==0.20.0
|
||||||
|
|
||||||
|
# Testing
|
||||||
pytest==7.4.3
|
pytest==7.4.3
|
||||||
pytest-flask==1.3.0
|
pytest-cov==4.1.0
|
||||||
coverage==7.3.2
|
|
||||||
|
# Development
|
||||||
|
Flask-DebugToolbar==0.13.1
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
redis==5.0.1
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
click==8.1.7
|
@ -1 +1,274 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test-Skript für MYP Backend-Setup
|
||||||
|
Überprüft, ob die neue Produktions-Konfiguration korrekt funktioniert
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
def test_python_environment():
|
||||||
|
"""Teste Python-Umgebung und Dependencies"""
|
||||||
|
print("🐍 Teste Python-Umgebung...")
|
||||||
|
|
||||||
|
# Python-Version prüfen
|
||||||
|
python_version = sys.version_info
|
||||||
|
print(f" Python-Version: {python_version.major}.{python_version.minor}.{python_version.micro}")
|
||||||
|
|
||||||
|
if python_version < (3, 8):
|
||||||
|
print(" ❌ Python-Version ist zu alt! Benötigt wird mindestens Python 3.8")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(" ✅ Python-Version ist kompatibel")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_dependencies():
|
||||||
|
"""Teste erforderliche Python-Pakete"""
|
||||||
|
print("📦 Teste Python-Dependencies...")
|
||||||
|
|
||||||
|
required_packages = [
|
||||||
|
'flask',
|
||||||
|
'flask_cors',
|
||||||
|
'werkzeug',
|
||||||
|
'pyjwt',
|
||||||
|
'python_dotenv',
|
||||||
|
'gunicorn'
|
||||||
|
]
|
||||||
|
|
||||||
|
missing_packages = []
|
||||||
|
|
||||||
|
for package in required_packages:
|
||||||
|
try:
|
||||||
|
__import__(package)
|
||||||
|
print(f" ✅ {package}")
|
||||||
|
except ImportError:
|
||||||
|
print(f" ❌ {package} fehlt")
|
||||||
|
missing_packages.append(package)
|
||||||
|
|
||||||
|
if missing_packages:
|
||||||
|
print(f" Fehlende Pakete: {', '.join(missing_packages)}")
|
||||||
|
print(" Installiere mit: pip install -r requirements.txt")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_configuration():
|
||||||
|
"""Teste Konfigurationsklassen"""
|
||||||
|
print("⚙️ Teste Konfiguration...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Importiere Konfiguration
|
||||||
|
from config import config, DevelopmentConfig, ProductionConfig, TestingConfig
|
||||||
|
|
||||||
|
print(" ✅ Konfigurationsklassen importiert")
|
||||||
|
|
||||||
|
# Teste verschiedene Konfigurationen
|
||||||
|
dev_config = DevelopmentConfig()
|
||||||
|
prod_config = ProductionConfig()
|
||||||
|
test_config = TestingConfig()
|
||||||
|
|
||||||
|
print(f" ✅ Development-Config: DEBUG={dev_config.DEBUG}")
|
||||||
|
print(f" ✅ Production-Config: DEBUG={prod_config.DEBUG}")
|
||||||
|
print(f" ✅ Testing-Config: TESTING={test_config.TESTING}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Konfigurationsfehler: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_app_factory():
|
||||||
|
"""Teste Application Factory Pattern"""
|
||||||
|
print("🏭 Teste Application Factory...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Temporäre Umgebungsvariablen setzen
|
||||||
|
os.environ['SECRET_KEY'] = 'test_secret_key'
|
||||||
|
os.environ['DATABASE_PATH'] = ':memory:'
|
||||||
|
|
||||||
|
from app import create_app
|
||||||
|
|
||||||
|
# Teste verschiedene Konfigurationen
|
||||||
|
dev_app = create_app('development')
|
||||||
|
prod_app = create_app('production')
|
||||||
|
test_app = create_app('testing')
|
||||||
|
|
||||||
|
print(f" ✅ Development-App: {dev_app.config['FLASK_ENV']}")
|
||||||
|
print(f" ✅ Production-App: {prod_app.config['FLASK_ENV']}")
|
||||||
|
print(f" ✅ Testing-App: {test_app.config['FLASK_ENV']}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Application Factory Fehler: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_database_functions():
|
||||||
|
"""Teste Datenbankfunktionen"""
|
||||||
|
print("🗄️ Teste Datenbankfunktionen...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.environ['SECRET_KEY'] = 'test_secret_key'
|
||||||
|
os.environ['DATABASE_PATH'] = ':memory:'
|
||||||
|
|
||||||
|
from app import create_app, init_db, get_db
|
||||||
|
|
||||||
|
app = create_app('testing')
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
# Initialisiere Test-Datenbank
|
||||||
|
init_db()
|
||||||
|
|
||||||
|
# Teste Datenbankverbindung
|
||||||
|
db = get_db()
|
||||||
|
result = db.execute('SELECT 1').fetchone()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print(" ✅ Datenbankverbindung funktioniert")
|
||||||
|
print(" ✅ Tabellen wurden erstellt")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(" ❌ Datenbankverbindung fehlgeschlagen")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Datenbankfehler: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_environment_variables():
|
||||||
|
"""Teste Umgebungsvariablen"""
|
||||||
|
print("🌍 Teste Umgebungsvariablen...")
|
||||||
|
|
||||||
|
# Lade env.backend falls vorhanden
|
||||||
|
if os.path.exists('env.backend'):
|
||||||
|
print(" ✅ env.backend gefunden")
|
||||||
|
|
||||||
|
with open('env.backend', 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
required_vars = [
|
||||||
|
'FLASK_APP',
|
||||||
|
'FLASK_ENV',
|
||||||
|
'SECRET_KEY',
|
||||||
|
'DATABASE_PATH'
|
||||||
|
]
|
||||||
|
|
||||||
|
found_vars = []
|
||||||
|
for line in lines:
|
||||||
|
if '=' in line and not line.strip().startswith('#'):
|
||||||
|
var_name = line.split('=')[0].strip()
|
||||||
|
if var_name in required_vars:
|
||||||
|
found_vars.append(var_name)
|
||||||
|
|
||||||
|
missing_vars = set(required_vars) - set(found_vars)
|
||||||
|
|
||||||
|
if missing_vars:
|
||||||
|
print(f" ❌ Fehlende Umgebungsvariablen: {', '.join(missing_vars)}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f" ✅ Alle erforderlichen Variablen gefunden: {', '.join(found_vars)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(" ❌ env.backend nicht gefunden")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_wsgi():
|
||||||
|
"""Teste WSGI-Konfiguration"""
|
||||||
|
print("🔧 Teste WSGI-Setup...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from wsgi import application
|
||||||
|
|
||||||
|
if application:
|
||||||
|
print(" ✅ WSGI-Application erfolgreich importiert")
|
||||||
|
print(f" ✅ App-Name: {application.name}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(" ❌ WSGI-Application ist None")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ WSGI-Fehler: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_health_endpoint():
|
||||||
|
"""Teste Health-Check-Endpoint"""
|
||||||
|
print("🏥 Teste Health-Check...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.environ['SECRET_KEY'] = 'test_secret_key'
|
||||||
|
os.environ['DATABASE_PATH'] = ':memory:'
|
||||||
|
|
||||||
|
from app import create_app
|
||||||
|
|
||||||
|
app = create_app('testing')
|
||||||
|
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get('/health')
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.get_json()
|
||||||
|
if data and data.get('status') == 'healthy':
|
||||||
|
print(" ✅ Health-Check funktioniert")
|
||||||
|
print(f" ✅ Service: {data.get('service')}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" ❌ Health-Check-Antwort fehlerhaft: {data}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f" ❌ Health-Check fehlgeschlagen: {response.status_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Health-Check-Fehler: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Haupttest-Funktion"""
|
||||||
|
print("=" * 50)
|
||||||
|
print("🧪 MYP Backend - Konfigurationstest")
|
||||||
|
print("=" * 50)
|
||||||
|
print()
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
test_python_environment,
|
||||||
|
test_dependencies,
|
||||||
|
test_configuration,
|
||||||
|
test_app_factory,
|
||||||
|
test_database_functions,
|
||||||
|
test_environment_variables,
|
||||||
|
test_wsgi,
|
||||||
|
test_health_endpoint
|
||||||
|
]
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for test in tests:
|
||||||
|
try:
|
||||||
|
if test():
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Test-Fehler: {e}")
|
||||||
|
failed += 1
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("=" * 50)
|
||||||
|
print(f"📊 Test-Ergebnisse: {passed} ✅ | {failed} ❌")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
if failed == 0:
|
||||||
|
print("🎉 Alle Tests bestanden! Backend ist bereit.")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("⚠️ Einige Tests fehlgeschlagen. Bitte Konfiguration prüfen.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
success = main()
|
||||||
|
sys.exit(0 if success else 1)
|
Loading…
x
Reference in New Issue
Block a user