""" Fixture and test data generation module. Generates realistic test data, mock objects, and fixtures for various scenarios. """ from typing import Dict, List, Any, Optional import json import random class FixtureGenerator: """Generate test fixtures and mock data.""" def __init__(self, seed: Optional[int] = None): """ Initialize fixture generator. Args: seed: Random seed for reproducible fixtures """ if seed is not None: random.seed(seed) def generate_boundary_values( self, data_type: str, constraints: Optional[Dict[str, Any]] = None ) -> List[Any]: """ Generate boundary values for testing. Args: data_type: Type of data (int, string, array, date, etc.) constraints: Constraints like min, max, length Returns: List of boundary values """ constraints = constraints or {} if data_type == "int": return self._integer_boundaries(constraints) elif data_type == "string": return self._string_boundaries(constraints) elif data_type == "array": return self._array_boundaries(constraints) elif data_type == "date": return self._date_boundaries(constraints) elif data_type == "email": return self._email_boundaries() elif data_type == "url": return self._url_boundaries() else: return [] def _integer_boundaries(self, constraints: Dict[str, Any]) -> List[int]: """Generate integer boundary values.""" min_val = constraints.get('min', 0) max_val = constraints.get('max', 100) boundaries = [ min_val, # Minimum min_val + 1, # Just above minimum max_val - 1, # Just below maximum max_val, # Maximum ] # Add special values if min_val <= 0 <= max_val: boundaries.append(0) # Zero if min_val < 0: boundaries.append(-1) # Negative return sorted(set(boundaries)) def _string_boundaries(self, constraints: Dict[str, Any]) -> List[str]: """Generate string boundary values.""" min_len = constraints.get('min_length', 0) max_len = constraints.get('max_length', 100) boundaries = [ "", # Empty string "a" * min_len, # Minimum length "a" * (min_len + 1) if min_len < max_len else "", # Just above minimum "a" * (max_len - 1) if max_len > 1 else "a", # Just below maximum "a" * max_len, # Maximum length "a" * (max_len + 1), # Exceeds maximum (invalid) ] # Add special characters if max_len >= 10: boundaries.append("test@#$%^&*()") # Special characters boundaries.append("unicode: 你好") # Unicode return [b for b in boundaries if b is not None] def _array_boundaries(self, constraints: Dict[str, Any]) -> List[List[Any]]: """Generate array boundary values.""" min_size = constraints.get('min_size', 0) max_size = constraints.get('max_size', 10) boundaries = [ [], # Empty array [1] * min_size, # Minimum size [1] * max_size, # Maximum size [1] * (max_size + 1), # Exceeds maximum (invalid) ] return boundaries def _date_boundaries(self, constraints: Dict[str, Any]) -> List[str]: """Generate date boundary values.""" return [ "1900-01-01", # Very old date "1970-01-01", # Unix epoch "2000-01-01", # Y2K "2025-11-05", # Today (example) "2099-12-31", # Far future "invalid-date", # Invalid format ] def _email_boundaries(self) -> List[str]: """Generate email boundary values.""" return [ "valid@example.com", # Valid "user.name+tag@example.co.uk", # Valid with special chars "invalid", # Missing @ "@example.com", # Missing local part "user@", # Missing domain "user@.com", # Invalid domain "", # Empty ] def _url_boundaries(self) -> List[str]: """Generate URL boundary values.""" return [ "https://example.com", # Valid HTTPS "http://example.com", # Valid HTTP "ftp://example.com", # Different protocol "//example.com", # Protocol-relative "example.com", # Missing protocol "", # Empty "not a url", # Invalid ] def generate_edge_cases( self, scenario: str, context: Optional[Dict[str, Any]] = None ) -> List[Dict[str, Any]]: """ Generate edge case test scenarios. Args: scenario: Type of scenario (auth, payment, form, api, etc.) context: Additional context for scenario Returns: List of edge case test scenarios """ if scenario == "auth": return self._auth_edge_cases() elif scenario == "payment": return self._payment_edge_cases() elif scenario == "form": return self._form_edge_cases(context or {}) elif scenario == "api": return self._api_edge_cases() elif scenario == "file_upload": return self._file_upload_edge_cases() else: return [] def _auth_edge_cases(self) -> List[Dict[str, Any]]: """Generate authentication edge cases.""" return [ { 'name': 'empty_credentials', 'input': {'username': '', 'password': ''}, 'expected': 'validation_error' }, { 'name': 'sql_injection_attempt', 'input': {'username': "admin' OR '1'='1", 'password': 'password'}, 'expected': 'authentication_failed' }, { 'name': 'very_long_password', 'input': {'username': 'user', 'password': 'a' * 1000}, 'expected': 'validation_error_or_success' }, { 'name': 'special_chars_username', 'input': {'username': 'user@#$%', 'password': 'password'}, 'expected': 'depends_on_validation' }, { 'name': 'unicode_credentials', 'input': {'username': '用户', 'password': 'пароль'}, 'expected': 'should_handle_unicode' } ] def _payment_edge_cases(self) -> List[Dict[str, Any]]: """Generate payment processing edge cases.""" return [ { 'name': 'zero_amount', 'input': {'amount': 0, 'currency': 'USD'}, 'expected': 'validation_error' }, { 'name': 'negative_amount', 'input': {'amount': -10, 'currency': 'USD'}, 'expected': 'validation_error' }, { 'name': 'very_large_amount', 'input': {'amount': 999999999.99, 'currency': 'USD'}, 'expected': 'should_handle_or_reject' }, { 'name': 'precision_test', 'input': {'amount': 10.999, 'currency': 'USD'}, 'expected': 'should_round_to_10.99' }, { 'name': 'invalid_currency', 'input': {'amount': 10, 'currency': 'XXX'}, 'expected': 'validation_error' } ] def _form_edge_cases(self, context: Dict[str, Any]) -> List[Dict[str, Any]]: """Generate form validation edge cases.""" fields = context.get('fields', []) edge_cases = [] for field in fields: field_name = field.get('name', 'field') field_type = field.get('type', 'text') edge_cases.append({ 'name': f'{field_name}_empty', 'input': {field_name: ''}, 'expected': 'validation_error_if_required' }) if field_type in ['text', 'email', 'password']: edge_cases.append({ 'name': f'{field_name}_very_long', 'input': {field_name: 'a' * 1000}, 'expected': 'validation_error_or_truncate' }) return edge_cases def _api_edge_cases(self) -> List[Dict[str, Any]]: """Generate API edge cases.""" return [ { 'name': 'missing_required_field', 'request': {'optional_field': 'value'}, 'expected': 400 }, { 'name': 'invalid_json', 'request': 'not valid json{', 'expected': 400 }, { 'name': 'empty_body', 'request': {}, 'expected': 400 }, { 'name': 'very_large_payload', 'request': {'data': 'x' * 1000000}, 'expected': '413_or_400' }, { 'name': 'invalid_method', 'method': 'INVALID', 'expected': 405 } ] def _file_upload_edge_cases(self) -> List[Dict[str, Any]]: """Generate file upload edge cases.""" return [ { 'name': 'empty_file', 'file': {'name': 'test.txt', 'size': 0}, 'expected': 'validation_error' }, { 'name': 'very_large_file', 'file': {'name': 'test.txt', 'size': 1000000000}, 'expected': 'size_limit_error' }, { 'name': 'invalid_extension', 'file': {'name': 'test.exe', 'size': 1000}, 'expected': 'validation_error' }, { 'name': 'no_extension', 'file': {'name': 'testfile', 'size': 1000}, 'expected': 'depends_on_validation' }, { 'name': 'special_chars_filename', 'file': {'name': 'test@#$%.txt', 'size': 1000}, 'expected': 'should_sanitize' } ] def generate_mock_data( self, schema: Dict[str, Any], count: int = 1 ) -> List[Dict[str, Any]]: """ Generate mock data based on schema. Args: schema: Schema definition with field types count: Number of mock objects to generate Returns: List of mock data objects """ mock_objects = [] for _ in range(count): mock_obj = {} for field_name, field_def in schema.items(): field_type = field_def.get('type', 'string') mock_obj[field_name] = self._generate_field_value(field_type, field_def) mock_objects.append(mock_obj) return mock_objects def _generate_field_value(self, field_type: str, field_def: Dict[str, Any]) -> Any: """Generate value for a single field.""" if field_type == "string": options = field_def.get('options') if options: return random.choice(options) return f"test_string_{random.randint(1, 1000)}" elif field_type == "int": min_val = field_def.get('min', 0) max_val = field_def.get('max', 100) return random.randint(min_val, max_val) elif field_type == "float": min_val = field_def.get('min', 0.0) max_val = field_def.get('max', 100.0) return round(random.uniform(min_val, max_val), 2) elif field_type == "bool": return random.choice([True, False]) elif field_type == "email": return f"user{random.randint(1, 1000)}@example.com" elif field_type == "date": return f"2025-{random.randint(1, 12):02d}-{random.randint(1, 28):02d}" elif field_type == "array": item_type = field_def.get('items', {}).get('type', 'string') size = random.randint(1, 5) return [self._generate_field_value(item_type, field_def.get('items', {})) for _ in range(size)] else: return None def generate_fixture_file( self, fixture_name: str, data: Any, format: str = "json" ) -> str: """ Generate fixture file content. Args: fixture_name: Name of fixture data: Fixture data format: Output format (json, yaml, python) Returns: Fixture file content as string """ if format == "json": return json.dumps(data, indent=2) elif format == "python": return f"""# {fixture_name} fixture {fixture_name.upper()} = {repr(data)} """ elif format == "yaml": # Simple YAML generation (for basic structures) return self._dict_to_yaml(data) else: return str(data) def _dict_to_yaml(self, data: Any, indent: int = 0) -> str: """Simple YAML generator.""" lines = [] indent_str = " " * indent if isinstance(data, dict): for key, value in data.items(): if isinstance(value, (dict, list)): lines.append(f"{indent_str}{key}:") lines.append(self._dict_to_yaml(value, indent + 1)) else: lines.append(f"{indent_str}{key}: {value}") elif isinstance(data, list): for item in data: if isinstance(item, dict): lines.append(f"{indent_str}-") lines.append(self._dict_to_yaml(item, indent + 1)) else: lines.append(f"{indent_str}- {item}") else: return str(data) return "\n".join(lines)