Initial commit
Dieser Commit ist enthalten in:
0
v2_adminpanel/tests/__init__.py
Normale Datei
0
v2_adminpanel/tests/__init__.py
Normale Datei
350
v2_adminpanel/tests/test_error_handling.py
Normale Datei
350
v2_adminpanel/tests/test_error_handling.py
Normale Datei
@ -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'])
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren