Dieser Commit ist enthalten in:
Claude Project Manager
2025-07-05 17:51:16 +02:00
Commit 0d7d888502
1594 geänderte Dateien mit 122839 neuen und 0 gelöschten Zeilen

Datei anzeigen

Datei anzeigen

@ -0,0 +1,350 @@
import pytest
import json
from datetime import datetime
from flask import Flask, request
from werkzeug.exceptions import NotFound
from ..core.exceptions import (
ValidationException, InputValidationError, AuthenticationException,
InvalidCredentialsError, DatabaseException, QueryError,
ResourceNotFoundError, BusinessRuleViolation
)
from ..core.error_handlers import init_error_handlers, handle_errors, validate_request
from ..core.validators import Validators, validate
from ..core.monitoring import error_metrics, alert_manager
from ..middleware.error_middleware import ErrorHandlingMiddleware
@pytest.fixture
def app():
app = Flask(__name__)
app.config['TESTING'] = True
app.config['SECRET_KEY'] = 'test-secret-key'
init_error_handlers(app)
ErrorHandlingMiddleware(app)
@app.route('/test-validation-error')
def test_validation_error():
raise InputValidationError(
field='email',
message='Invalid email format',
value='not-an-email'
)
@app.route('/test-auth-error')
def test_auth_error():
raise InvalidCredentialsError('testuser')
@app.route('/test-db-error')
def test_db_error():
raise QueryError(
message='Column not found',
query='SELECT * FROM users',
error_code='42703'
)
@app.route('/test-not-found')
def test_not_found():
raise ResourceNotFoundError('User', 123)
@app.route('/test-generic-error')
def test_generic_error():
raise Exception('Something went wrong')
@app.route('/test-validate', methods=['POST'])
@validate({
'email': {'type': 'email', 'required': True},
'age': {'type': 'integer', 'required': True, 'min_value': 18},
'username': {'type': 'username', 'required': False}
})
def test_validate():
return {'success': True, 'data': request.validated_data}
@app.route('/test-handle-errors')
@handle_errors(
catch=(ValueError,),
message='Value error occurred',
user_message='Ungültiger Wert'
)
def test_handle_errors():
raise ValueError('Invalid value')
return app
@pytest.fixture
def client(app):
return app.test_client()
class TestExceptions:
def test_validation_exception(self):
exc = ValidationException(
message='Test validation error',
field='test_field',
value='test_value',
user_message='Test user message'
)
assert exc.code == 'VALIDATION_ERROR'
assert exc.status_code == 400
assert exc.details['field'] == 'test_field'
assert exc.details['value'] == 'test_value'
assert exc.user_message == 'Test user message'
def test_input_validation_error(self):
exc = InputValidationError(
field='email',
message='Invalid format',
value='not-email',
expected_type='email'
)
assert exc.code == 'VALIDATION_ERROR'
assert exc.status_code == 400
assert exc.details['field'] == 'email'
assert exc.details['expected_type'] == 'email'
def test_business_rule_violation(self):
exc = BusinessRuleViolation(
rule='max_licenses',
message='License limit exceeded',
context={'current': 10, 'max': 5}
)
assert exc.details['rule'] == 'max_licenses'
assert exc.details['context']['current'] == 10
def test_authentication_exceptions(self):
exc = InvalidCredentialsError('testuser')
assert exc.status_code == 401
assert exc.details['username'] == 'testuser'
def test_database_exceptions(self):
exc = QueryError(
message='Syntax error',
query='SELECT * FORM users',
error_code='42601'
)
assert exc.code == 'DATABASE_ERROR'
assert exc.status_code == 500
assert 'query_hash' in exc.details
assert exc.details['error_code'] == '42601'
def test_resource_not_found(self):
exc = ResourceNotFoundError(
resource_type='License',
resource_id='ABC123',
search_criteria={'customer_id': 1}
)
assert exc.status_code == 404
assert exc.details['resource_type'] == 'License'
assert exc.details['resource_id'] == 'ABC123'
assert exc.details['search_criteria']['customer_id'] == 1
class TestErrorHandlers:
def test_validation_error_json_response(self, client):
response = client.get(
'/test-validation-error',
headers={'Accept': 'application/json'}
)
assert response.status_code == 400
data = json.loads(response.data)
assert 'error' in data
assert data['error']['code'] == 'VALIDATION_ERROR'
assert 'request_id' in data['error']
def test_validation_error_html_response(self, client):
response = client.get('/test-validation-error')
assert response.status_code == 400
assert b'Ungültiger Wert für Feld' in response.data
def test_auth_error_response(self, client):
response = client.get(
'/test-auth-error',
headers={'Accept': 'application/json'}
)
assert response.status_code == 401
data = json.loads(response.data)
assert data['error']['code'] == 'AUTHENTICATION_ERROR'
def test_db_error_response(self, client):
response = client.get(
'/test-db-error',
headers={'Accept': 'application/json'}
)
assert response.status_code == 500
data = json.loads(response.data)
assert data['error']['code'] == 'DATABASE_ERROR'
def test_not_found_response(self, client):
response = client.get('/test-not-found')
assert response.status_code == 404
assert b'User nicht gefunden' in response.data
def test_generic_error_response(self, client):
response = client.get(
'/test-generic-error',
headers={'Accept': 'application/json'}
)
assert response.status_code == 500
data = json.loads(response.data)
assert data['error']['code'] == 'INTERNAL_ERROR'
def test_validate_decorator_success(self, client):
response = client.post(
'/test-validate',
json={'email': 'test@example.com', 'age': 25, 'username': 'testuser'}
)
assert response.status_code == 200
data = json.loads(response.data)
assert data['success'] is True
assert data['data']['email'] == 'test@example.com'
assert data['data']['age'] == 25
def test_validate_decorator_failure(self, client):
response = client.post(
'/test-validate',
json={'email': 'invalid-email', 'age': 15}
)
assert response.status_code == 400
data = json.loads(response.data)
assert data['error']['code'] == 'VALIDATION_ERROR'
def test_handle_errors_decorator(self, client):
response = client.get('/test-handle-errors')
assert response.status_code == 500
assert b'Ungültiger Wert' in response.data
class TestValidators:
def test_email_validator(self):
assert Validators.email('test@example.com') == 'test@example.com'
assert Validators.email('TEST@EXAMPLE.COM') == 'test@example.com'
with pytest.raises(InputValidationError):
Validators.email('invalid-email')
with pytest.raises(InputValidationError):
Validators.email('')
def test_phone_validator(self):
assert Validators.phone('+1-234-567-8900') == '+12345678900'
assert Validators.phone('(123) 456-7890') == '1234567890'
with pytest.raises(InputValidationError):
Validators.phone('123')
def test_license_key_validator(self):
assert Validators.license_key('abcd-1234-efgh-5678') == 'ABCD-1234-EFGH-5678'
with pytest.raises(InputValidationError):
Validators.license_key('invalid-key')
def test_integer_validator(self):
assert Validators.integer('123') == 123
assert Validators.integer(456) == 456
assert Validators.integer('10', min_value=5, max_value=15) == 10
with pytest.raises(InputValidationError):
Validators.integer('abc')
with pytest.raises(InputValidationError):
Validators.integer('3', min_value=5)
with pytest.raises(InputValidationError):
Validators.integer('20', max_value=15)
def test_string_validator(self):
assert Validators.string('test', min_length=2, max_length=10) == 'test'
with pytest.raises(InputValidationError):
Validators.string('a', min_length=2)
with pytest.raises(InputValidationError):
Validators.string('a' * 20, max_length=10)
with pytest.raises(InputValidationError):
Validators.string('test<script>', safe_only=True)
def test_password_validator(self):
assert Validators.password('Test123!@#') == 'Test123!@#'
with pytest.raises(InputValidationError) as exc:
Validators.password('weak')
assert 'at least 8 characters' in str(exc.value)
with pytest.raises(InputValidationError) as exc:
Validators.password('alllowercase123!')
assert 'uppercase letter' in str(exc.value)
def test_ip_address_validator(self):
assert Validators.ip_address('192.168.1.1') == '192.168.1.1'
assert Validators.ip_address('::1') == '::1'
with pytest.raises(InputValidationError):
Validators.ip_address('999.999.999.999')
with pytest.raises(InputValidationError):
Validators.ip_address('::1', version=4)
def test_url_validator(self):
assert Validators.url('http://example.com') == 'http://example.com'
assert Validators.url('https://example.com/path') == 'https://example.com/path'
with pytest.raises(InputValidationError):
Validators.url('not-a-url')
with pytest.raises(InputValidationError):
Validators.url('http://example.com', require_https=True)
def test_enum_validator(self):
assert Validators.enum('active', 'status', ['active', 'inactive']) == 'active'
with pytest.raises(InputValidationError):
Validators.enum('pending', 'status', ['active', 'inactive'])
class TestMonitoring:
def test_error_metrics_recording(self, client):
initial_count = error_metrics.error_counter._value._value
client.get('/test-validation-error')
new_count = error_metrics.error_counter._value._value
assert new_count > initial_count
def test_alert_generation(self):
for i in range(15):
error = ValidationException(
message=f'Test error {i}',
code='TEST_ERROR'
)
error_metrics.record_error(error, 'test_endpoint')
alerts = alert_manager.check_alerts(error_metrics)
assert any(a['type'] == 'high_error_rate' for a in alerts)
def test_metrics_endpoint(self, client):
response = client.get('/metrics')
assert response.status_code == 200
assert b'app_errors_total' in response.data
assert b'app_request_duration_seconds' in response.data
if __name__ == '__main__':
pytest.main([__file__, '-v'])