""" Test cases for input validation utilities. """ import pytest from unittest.mock import Mock, patch, MagicMock from flask import Flask, request import json import tempfile import os # Import the modules to test try: from src.server.web.controllers.shared.validators import ( validate_json_input, validate_query_params, validate_pagination_params, validate_anime_data, validate_file_upload, is_valid_url, is_valid_email, sanitize_string, validate_id_parameter ) except ImportError: # Fallback for testing validate_json_input = None validate_query_params = None validate_pagination_params = None validate_anime_data = None validate_file_upload = None is_valid_url = None is_valid_email = None sanitize_string = None validate_id_parameter = None class TestValidationDecorators: """Test cases for validation decorators.""" @pytest.fixture def app(self): """Create a test Flask application.""" app = Flask(__name__) app.config['TESTING'] = True return app @pytest.fixture def client(self, app): """Create a test client.""" return app.test_client() def test_validate_json_input_success(self, app, client): """Test validate_json_input decorator with valid JSON.""" if not validate_json_input: pytest.skip("Module not available") @app.route('/test', methods=['POST']) @validate_json_input( required_fields=['name'], optional_fields=['description'], field_types={'name': str, 'description': str} ) def test_endpoint(): return {'status': 'success'} response = client.post('/test', json={'name': 'Test Name', 'description': 'Test Description'}, content_type='application/json') assert response.status_code == 200 def test_validate_json_input_missing_required(self, app, client): """Test validate_json_input with missing required field.""" if not validate_json_input: pytest.skip("Module not available") @app.route('/test', methods=['POST']) @validate_json_input(required_fields=['name']) def test_endpoint(): return {'status': 'success'} response = client.post('/test', json={'description': 'Test Description'}, content_type='application/json') assert response.status_code == 400 data = json.loads(response.data) assert 'Missing required fields' in data[0]['message'] def test_validate_json_input_wrong_type(self, app, client): """Test validate_json_input with wrong field type.""" if not validate_json_input: pytest.skip("Module not available") @app.route('/test', methods=['POST']) @validate_json_input( required_fields=['age'], field_types={'age': int} ) def test_endpoint(): return {'status': 'success'} response = client.post('/test', json={'age': 'twenty'}, content_type='application/json') assert response.status_code == 400 data = json.loads(response.data) assert 'Type validation failed' in data[0]['message'] def test_validate_json_input_unexpected_fields(self, app, client): """Test validate_json_input with unexpected fields.""" if not validate_json_input: pytest.skip("Module not available") @app.route('/test', methods=['POST']) @validate_json_input( required_fields=['name'], optional_fields=['description'] ) def test_endpoint(): return {'status': 'success'} response = client.post('/test', json={'name': 'Test', 'description': 'Test', 'extra_field': 'unexpected'}, content_type='application/json') assert response.status_code == 400 data = json.loads(response.data) assert 'Unexpected fields' in data[0]['message'] def test_validate_json_input_not_json(self, app, client): """Test validate_json_input with non-JSON content.""" if not validate_json_input: pytest.skip("Module not available") @app.route('/test', methods=['POST']) @validate_json_input(required_fields=['name']) def test_endpoint(): return {'status': 'success'} response = client.post('/test', data='not json') assert response.status_code == 400 data = json.loads(response.data) assert 'Request must be JSON' in data[0]['message'] def test_validate_query_params_success(self, app, client): """Test validate_query_params decorator with valid parameters.""" if not validate_query_params: pytest.skip("Module not available") @app.route('/test') @validate_query_params( allowed_params=['page', 'limit'], required_params=['page'], param_types={'page': int, 'limit': int} ) def test_endpoint(): return {'status': 'success'} response = client.get('/test?page=1&limit=10') assert response.status_code == 200 def test_validate_query_params_missing_required(self, app, client): """Test validate_query_params with missing required parameter.""" if not validate_query_params: pytest.skip("Module not available") @app.route('/test') @validate_query_params(required_params=['page']) def test_endpoint(): return {'status': 'success'} response = client.get('/test') assert response.status_code == 400 data = json.loads(response.data) assert 'Missing required parameters' in data[0]['message'] def test_validate_query_params_unexpected(self, app, client): """Test validate_query_params with unexpected parameters.""" if not validate_query_params: pytest.skip("Module not available") @app.route('/test') @validate_query_params(allowed_params=['page']) def test_endpoint(): return {'status': 'success'} response = client.get('/test?page=1&unexpected=value') assert response.status_code == 400 data = json.loads(response.data) assert 'Unexpected parameters' in data[0]['message'] def test_validate_query_params_wrong_type(self, app, client): """Test validate_query_params with wrong parameter type.""" if not validate_query_params: pytest.skip("Module not available") @app.route('/test') @validate_query_params( allowed_params=['page'], param_types={'page': int} ) def test_endpoint(): return {'status': 'success'} response = client.get('/test?page=abc') assert response.status_code == 400 data = json.loads(response.data) assert 'Parameter type validation failed' in data[0]['message'] def test_validate_pagination_params_success(self, app, client): """Test validate_pagination_params decorator with valid parameters.""" if not validate_pagination_params: pytest.skip("Module not available") @app.route('/test') @validate_pagination_params def test_endpoint(): return {'status': 'success'} response = client.get('/test?page=1&per_page=10&limit=20&offset=5') assert response.status_code == 200 def test_validate_pagination_params_invalid_page(self, app, client): """Test validate_pagination_params with invalid page.""" if not validate_pagination_params: pytest.skip("Module not available") @app.route('/test') @validate_pagination_params def test_endpoint(): return {'status': 'success'} response = client.get('/test?page=0') assert response.status_code == 400 data = json.loads(response.data) assert 'page must be greater than 0' in data[0]['errors'] def test_validate_pagination_params_invalid_per_page(self, app, client): """Test validate_pagination_params with invalid per_page.""" if not validate_pagination_params: pytest.skip("Module not available") @app.route('/test') @validate_pagination_params def test_endpoint(): return {'status': 'success'} response = client.get('/test?per_page=2000') assert response.status_code == 400 data = json.loads(response.data) assert 'per_page cannot exceed 1000' in data[0]['errors'] def test_validate_id_parameter_success(self, app, client): """Test validate_id_parameter decorator with valid ID.""" if not validate_id_parameter: pytest.skip("Module not available") @app.route('/test/') @validate_id_parameter('id') def test_endpoint(id): return {'status': 'success', 'id': id} response = client.get('/test/123') assert response.status_code == 200 data = json.loads(response.data) assert data['id'] == 123 def test_validate_id_parameter_invalid(self, app): """Test validate_id_parameter decorator with invalid ID.""" if not validate_id_parameter: pytest.skip("Module not available") @validate_id_parameter('id') def test_function(id='abc'): return {'status': 'success'} # Since this is a decorator that modifies kwargs, # we test it directly result = test_function(id='abc') # Should return error response assert result[1] == 400 class TestValidationUtilities: """Test cases for validation utility functions.""" def test_validate_anime_data_valid(self): """Test validate_anime_data with valid data.""" if not validate_anime_data: pytest.skip("Module not available") data = { 'name': 'Test Anime', 'url': 'https://example.com/anime/test', 'description': 'A test anime', 'episodes': 12, 'status': 'completed' } errors = validate_anime_data(data) assert len(errors) == 0 def test_validate_anime_data_missing_required(self): """Test validate_anime_data with missing required fields.""" if not validate_anime_data: pytest.skip("Module not available") data = { 'description': 'A test anime' } errors = validate_anime_data(data) assert len(errors) > 0 assert any('Missing required field: name' in error for error in errors) assert any('Missing required field: url' in error for error in errors) def test_validate_anime_data_invalid_types(self): """Test validate_anime_data with invalid field types.""" if not validate_anime_data: pytest.skip("Module not available") data = { 'name': 123, # Should be string 'url': 'invalid-url', # Should be valid URL 'episodes': 'twelve' # Should be integer } errors = validate_anime_data(data) assert len(errors) > 0 assert any('name must be a string' in error for error in errors) assert any('url must be a valid URL' in error for error in errors) assert any('episodes must be an integer' in error for error in errors) def test_validate_anime_data_invalid_status(self): """Test validate_anime_data with invalid status.""" if not validate_anime_data: pytest.skip("Module not available") data = { 'name': 'Test Anime', 'url': 'https://example.com/anime/test', 'status': 'invalid_status' } errors = validate_anime_data(data) assert len(errors) > 0 assert any('status must be one of' in error for error in errors) def test_validate_file_upload_valid(self): """Test validate_file_upload with valid file.""" if not validate_file_upload: pytest.skip("Module not available") # Create a mock file object mock_file = Mock() mock_file.filename = 'test.txt' mock_file.content_length = 1024 # 1KB errors = validate_file_upload(mock_file, allowed_extensions=['txt'], max_size_mb=1) assert len(errors) == 0 def test_validate_file_upload_no_file(self): """Test validate_file_upload with no file.""" if not validate_file_upload: pytest.skip("Module not available") errors = validate_file_upload(None) assert len(errors) > 0 assert 'No file provided' in errors def test_validate_file_upload_empty_filename(self): """Test validate_file_upload with empty filename.""" if not validate_file_upload: pytest.skip("Module not available") mock_file = Mock() mock_file.filename = '' errors = validate_file_upload(mock_file) assert len(errors) > 0 assert 'No file selected' in errors def test_validate_file_upload_invalid_extension(self): """Test validate_file_upload with invalid extension.""" if not validate_file_upload: pytest.skip("Module not available") mock_file = Mock() mock_file.filename = 'test.exe' errors = validate_file_upload(mock_file, allowed_extensions=['txt', 'pdf']) assert len(errors) > 0 assert 'File type not allowed' in errors[0] def test_validate_file_upload_too_large(self): """Test validate_file_upload with file too large.""" if not validate_file_upload: pytest.skip("Module not available") mock_file = Mock() mock_file.filename = 'test.txt' mock_file.content_length = 5 * 1024 * 1024 # 5MB errors = validate_file_upload(mock_file, max_size_mb=1) assert len(errors) > 0 assert 'File size exceeds maximum' in errors[0] def test_is_valid_url_valid(self): """Test is_valid_url with valid URLs.""" if not is_valid_url: pytest.skip("Module not available") valid_urls = [ 'https://example.com', 'http://test.co.uk', 'https://subdomain.example.com/path', 'http://localhost:8080', 'https://192.168.1.1:3000/api' ] for url in valid_urls: assert is_valid_url(url), f"URL should be valid: {url}" def test_is_valid_url_invalid(self): """Test is_valid_url with invalid URLs.""" if not is_valid_url: pytest.skip("Module not available") invalid_urls = [ 'not-a-url', 'ftp://example.com', # Only http/https supported 'https://', 'http://.', 'just-text' ] for url in invalid_urls: assert not is_valid_url(url), f"URL should be invalid: {url}" def test_is_valid_email_valid(self): """Test is_valid_email with valid emails.""" if not is_valid_email: pytest.skip("Module not available") valid_emails = [ 'test@example.com', 'user.name@domain.co.uk', 'admin+tag@site.org', 'user123@test-domain.com' ] for email in valid_emails: assert is_valid_email(email), f"Email should be valid: {email}" def test_is_valid_email_invalid(self): """Test is_valid_email with invalid emails.""" if not is_valid_email: pytest.skip("Module not available") invalid_emails = [ 'not-an-email', '@domain.com', 'user@', 'user@domain', 'user space@domain.com' ] for email in invalid_emails: assert not is_valid_email(email), f"Email should be invalid: {email}" def test_sanitize_string_basic(self): """Test sanitize_string with basic input.""" if not sanitize_string: pytest.skip("Module not available") result = sanitize_string(" Hello World ") assert result == "Hello World" def test_sanitize_string_max_length(self): """Test sanitize_string with max length.""" if not sanitize_string: pytest.skip("Module not available") long_string = "A" * 100 result = sanitize_string(long_string, max_length=50) assert len(result) == 50 assert result == "A" * 50 def test_sanitize_string_control_characters(self): """Test sanitize_string removes control characters.""" if not sanitize_string: pytest.skip("Module not available") input_string = "Hello\x00World\x01Test" result = sanitize_string(input_string) assert result == "HelloWorldTest" def test_sanitize_string_non_string(self): """Test sanitize_string with non-string input.""" if not sanitize_string: pytest.skip("Module not available") result = sanitize_string(123) assert result == "123" class TestValidatorIntegration: """Integration tests for validators.""" @pytest.fixture def app(self): """Create a test Flask application.""" app = Flask(__name__) app.config['TESTING'] = True return app @pytest.fixture def client(self, app): """Create a test client.""" return app.test_client() def test_multiple_validators(self, app, client): """Test using multiple validators on the same endpoint.""" if not validate_json_input or not validate_pagination_params: pytest.skip("Module not available") @app.route('/test', methods=['POST']) @validate_json_input(required_fields=['name']) @validate_pagination_params def test_endpoint(): return {'status': 'success'} response = client.post('/test?page=1&per_page=10', json={'name': 'Test'}, content_type='application/json') assert response.status_code == 200 def test_validator_preserves_metadata(self): """Test that validators preserve function metadata.""" if not validate_json_input: pytest.skip("Module not available") @validate_json_input(required_fields=['name']) def test_function(): """Test function docstring.""" return "test" assert test_function.__name__ == "test_function" assert test_function.__doc__ == "Test function docstring." if __name__ == '__main__': pytest.main([__file__])