feat: Phase 3 Tasks 16-18 — Pydantic-Settings, Error-Handler, systemd
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""Test für src.config — Pydantic-Settings Konfiguration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from src.config import Settings, settings
|
||||
|
||||
|
||||
class TestSettings:
|
||||
"""Tests für die zentrale Settings-Klasse."""
|
||||
|
||||
@patch.dict(os.environ, {"REDIS_HOST": "redis.example.com", "REDIS_PORT": "6380"})
|
||||
def test_settings_liest_env_vars(self) -> None:
|
||||
class EnvSettings(Settings):
|
||||
model_config = {"env_file": None}
|
||||
|
||||
env = EnvSettings()
|
||||
assert env.REDIS_HOST == "redis.example.com"
|
||||
assert env.REDIS_PORT == 6380
|
||||
|
||||
def test_settings_standardwerte(self) -> None:
|
||||
"""Standardwerte ohne Umgebungsvariablen."""
|
||||
|
||||
class EnvSettings(Settings):
|
||||
model_config = {"env_file": None}
|
||||
|
||||
env = EnvSettings()
|
||||
assert env.REDIS_HOST == "localhost"
|
||||
assert env.REDIS_PORT == 6379
|
||||
assert env.APP_HOST == "0.0.0.0"
|
||||
assert env.APP_PORT == 5002
|
||||
assert env.APP_DEBUG is False
|
||||
|
||||
def test_settings_falsches_redis_port_wirft(self) -> None:
|
||||
"""Ungültiger Port-Typ wirft ValidationError."""
|
||||
with pytest.raises(Exception): # type: ignore[misc]
|
||||
with patch.dict(os.environ, {"REDIS_PORT": "nicht_zahl"}, clear=True):
|
||||
Settings._build_customise_schema()
|
||||
Settings.model_rebuild(force=True)
|
||||
env = Settings(model_config={"env_file": None})
|
||||
_ = env.REDIS_PORT # noqa: B018
|
||||
|
||||
def test_settings_globale_instance(self) -> None:
|
||||
"""globale settings-Instanz ist vom Typ Settings."""
|
||||
assert isinstance(settings, Settings)
|
||||
assert settings.REDIS_HOST == "localhost"
|
||||
assert settings.APP_PORT == 5002
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Tests für src.error_handler — Einheitliche JSON-Error-Responses."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from src.app import create_app
|
||||
from src.config import settings
|
||||
from src.error_handler import register_error_handlers
|
||||
|
||||
|
||||
class TestErrorHandlers:
|
||||
"""Tests fuer zentrale Error-Handler."""
|
||||
|
||||
def setup_method(self) -> None:
|
||||
"""Jeder Test bekommt eine frische App mit Error-Handlern."""
|
||||
self.app = create_app()
|
||||
self.app.queue_handler.redis.close() # Redis-Verbindung trennen
|
||||
self.client = self.app.test_client()
|
||||
|
||||
def test_404_returnt_json(self) -> None:
|
||||
"""Nichtexistente Route → 404 mit JSON."""
|
||||
response = self.client.get("/nicht_existent")
|
||||
assert response.status_code == 404
|
||||
data = response.get_json()
|
||||
assert data["error"] == "Not Found"
|
||||
assert isinstance(data["message"], str)
|
||||
assert data["status"] == 404
|
||||
|
||||
def test_400_returnt_json(self) -> None:
|
||||
"""HTTP 400 → JSON-Response mit einheitlichem Format."""
|
||||
|
||||
@self.app.route("/abort400")
|
||||
def abort400():
|
||||
raise BadRequest("Ungueltige Eingabe")
|
||||
|
||||
response = self.client.get("/abort400")
|
||||
assert response.status_code == 400
|
||||
data = response.get_json()
|
||||
assert data["error"] == "Bad Request"
|
||||
assert data["status"] == 400
|
||||
|
||||
def test_422_returnt_json(self) -> None:
|
||||
"""ValidationError → 422 mit JSON."""
|
||||
|
||||
class UserSchema(BaseModel):
|
||||
name: str
|
||||
|
||||
@self.app.route("/validate", methods=["POST"])
|
||||
def validate():
|
||||
UserSchema.model_validate({})
|
||||
return "ok"
|
||||
|
||||
response = self.client.post("/validate", json={})
|
||||
assert response.status_code == 422
|
||||
data = response.get_json()
|
||||
assert data["status"] == 422
|
||||
assert "error" in data
|
||||
|
||||
def test_500_returnt_json(self) -> None:
|
||||
"""Interner Serverfehler → 500 mit JSON."""
|
||||
|
||||
@self.app.route("/crash")
|
||||
def crash():
|
||||
raise RuntimeError("Test-Fehler")
|
||||
|
||||
response = self.client.get("/crash")
|
||||
assert response.status_code == 500
|
||||
data = response.get_json()
|
||||
assert data["error"] == "Internal Server Error"
|
||||
assert data["status"] == 500
|
||||
|
||||
def test_error_handler_alle_status_codes(self) -> None:
|
||||
"""Alle Error-Handler sind registriert."""
|
||||
specs = self.app.error_handler_spec
|
||||
found = set()
|
||||
for blueprint_name, codes in (specs or {}).items():
|
||||
if bp_codes := codes:
|
||||
for code in bp_codes:
|
||||
if code is not None:
|
||||
found.add(int(code))
|
||||
for required in (400, 401, 404, 422, 500):
|
||||
assert required in found, f"Error handler {required} fehlt"
|
||||
Reference in New Issue
Block a user