281 lines
10 KiB
Python
281 lines
10 KiB
Python
"""
|
|
Integration tests to verify no route conflicts exist.
|
|
|
|
This module ensures that all routes are unique and properly configured
|
|
after consolidation efforts.
|
|
"""
|
|
|
|
import pytest
|
|
import sys
|
|
import os
|
|
from typing import Dict, List, Tuple, Set
|
|
from collections import defaultdict
|
|
|
|
# Add src to path for imports
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src'))
|
|
|
|
|
|
class TestRouteConflicts:
|
|
"""Test suite to detect and prevent route conflicts."""
|
|
|
|
def setup_method(self):
|
|
"""Setup test fixtures."""
|
|
self.route_registry = defaultdict(list)
|
|
self.blueprint_routes = {}
|
|
|
|
def test_no_duplicate_routes(self):
|
|
"""
|
|
Ensure no route conflicts exist across all controllers.
|
|
|
|
This test scans all controller files for route definitions
|
|
and verifies that no two routes have the same path and method.
|
|
"""
|
|
routes = self._extract_all_routes()
|
|
conflicts = self._find_route_conflicts(routes)
|
|
|
|
assert len(conflicts) == 0, f"Route conflicts found: {conflicts}"
|
|
|
|
def test_url_prefix_consistency(self):
|
|
"""
|
|
Test that URL prefixes follow consistent patterns.
|
|
|
|
Verifies that all API routes follow the /api/v1/ prefix pattern
|
|
where appropriate.
|
|
"""
|
|
routes = self._extract_all_routes()
|
|
inconsistent_routes = []
|
|
|
|
for route_info in routes:
|
|
path = route_info['path']
|
|
controller = route_info['controller']
|
|
|
|
# Skip non-API routes
|
|
if not path.startswith('/api/'):
|
|
continue
|
|
|
|
# Check for version consistency
|
|
if path.startswith('/api/') and not path.startswith('/api/v1/'):
|
|
# Some exceptions are allowed (like /api/health)
|
|
allowed_exceptions = ['/api/health', '/api/config', '/api/scheduler', '/api/logging']
|
|
if not any(path.startswith(exc) for exc in allowed_exceptions):
|
|
inconsistent_routes.append({
|
|
'path': path,
|
|
'controller': controller,
|
|
'issue': 'Missing version prefix'
|
|
})
|
|
|
|
# This is a warning test - inconsistencies should be noted but not fail
|
|
if inconsistent_routes:
|
|
print(f"URL prefix inconsistencies found (consider standardizing): {inconsistent_routes}")
|
|
|
|
def test_blueprint_name_uniqueness(self):
|
|
"""
|
|
Test that all Blueprint names are unique.
|
|
|
|
Ensures no Blueprint naming conflicts exist.
|
|
"""
|
|
blueprint_names = self._extract_blueprint_names()
|
|
duplicates = self._find_duplicates(blueprint_names)
|
|
|
|
assert len(duplicates) == 0, f"Duplicate blueprint names found: {duplicates}"
|
|
|
|
def test_route_parameter_consistency(self):
|
|
"""
|
|
Test that route parameters follow consistent naming patterns.
|
|
|
|
Ensures parameters like {id} vs {episode_id} are used consistently.
|
|
"""
|
|
routes = self._extract_all_routes()
|
|
parameter_patterns = defaultdict(set)
|
|
|
|
for route_info in routes:
|
|
path = route_info['path']
|
|
# Extract parameter patterns
|
|
if '<' in path:
|
|
# Extract parameter names like <int:episode_id>
|
|
import re
|
|
params = re.findall(r'<[^>]+>', path)
|
|
for param in params:
|
|
# Normalize parameter (remove type hints)
|
|
clean_param = param.replace('<int:', '<').replace('<string:', '<').replace('<', '').replace('>', '')
|
|
parameter_patterns[clean_param].add(route_info['controller'])
|
|
|
|
# Check for inconsistent ID naming
|
|
id_patterns = {k: v for k, v in parameter_patterns.items() if 'id' in k}
|
|
if len(id_patterns) > 3: # Allow some variation
|
|
print(f"Consider standardizing ID parameter naming: {dict(id_patterns)}")
|
|
|
|
def test_http_method_coverage(self):
|
|
"""
|
|
Test that CRUD operations are consistently implemented.
|
|
|
|
Ensures that resources supporting CRUD have all necessary methods.
|
|
"""
|
|
routes = self._extract_all_routes()
|
|
resource_methods = defaultdict(set)
|
|
|
|
for route_info in routes:
|
|
path = route_info['path']
|
|
method = route_info['method']
|
|
|
|
# Group by resource (extract base path)
|
|
if '/api/v1/' in path:
|
|
resource = path.split('/api/v1/')[1].split('/')[0]
|
|
resource_methods[resource].add(method)
|
|
|
|
# Check for incomplete CRUD implementations
|
|
incomplete_crud = {}
|
|
for resource, methods in resource_methods.items():
|
|
if 'GET' in methods or 'POST' in methods: # If it has read/write operations
|
|
missing_methods = {'GET', 'POST', 'PUT', 'DELETE'} - methods
|
|
if missing_methods:
|
|
incomplete_crud[resource] = missing_methods
|
|
|
|
# This is informational - not all resources need full CRUD
|
|
if incomplete_crud:
|
|
print(f"Resources with incomplete CRUD operations: {incomplete_crud}")
|
|
|
|
def _extract_all_routes(self) -> List[Dict]:
|
|
"""
|
|
Extract all route definitions from controller files.
|
|
|
|
Returns:
|
|
List of route information dictionaries
|
|
"""
|
|
routes = []
|
|
controller_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src', 'server', 'web', 'controllers')
|
|
|
|
# This would normally scan actual controller files
|
|
# For now, return mock data based on our analysis
|
|
mock_routes = [
|
|
{'path': '/api/v1/anime', 'method': 'GET', 'controller': 'anime.py', 'function': 'list_anime'},
|
|
{'path': '/api/v1/anime', 'method': 'POST', 'controller': 'anime.py', 'function': 'create_anime'},
|
|
{'path': '/api/v1/anime/<int:id>', 'method': 'GET', 'controller': 'anime.py', 'function': 'get_anime'},
|
|
{'path': '/api/v1/episodes', 'method': 'GET', 'controller': 'episodes.py', 'function': 'list_episodes'},
|
|
{'path': '/api/v1/episodes', 'method': 'POST', 'controller': 'episodes.py', 'function': 'create_episode'},
|
|
{'path': '/api/health', 'method': 'GET', 'controller': 'health.py', 'function': 'health_check'},
|
|
{'path': '/api/health/system', 'method': 'GET', 'controller': 'health.py', 'function': 'system_health'},
|
|
{'path': '/status', 'method': 'GET', 'controller': 'health.py', 'function': 'basic_status'},
|
|
{'path': '/ping', 'method': 'GET', 'controller': 'health.py', 'function': 'ping'},
|
|
]
|
|
|
|
return mock_routes
|
|
|
|
def _find_route_conflicts(self, routes: List[Dict]) -> List[Dict]:
|
|
"""
|
|
Find conflicting routes (same path and method).
|
|
|
|
Args:
|
|
routes: List of route information
|
|
|
|
Returns:
|
|
List of conflicts found
|
|
"""
|
|
route_map = {}
|
|
conflicts = []
|
|
|
|
for route in routes:
|
|
key = (route['path'], route['method'])
|
|
if key in route_map:
|
|
conflicts.append({
|
|
'path': route['path'],
|
|
'method': route['method'],
|
|
'controllers': [route_map[key]['controller'], route['controller']]
|
|
})
|
|
else:
|
|
route_map[key] = route
|
|
|
|
return conflicts
|
|
|
|
def _extract_blueprint_names(self) -> List[Tuple[str, str]]:
|
|
"""
|
|
Extract all Blueprint names from controller files.
|
|
|
|
Returns:
|
|
List of (blueprint_name, controller_file) tuples
|
|
"""
|
|
# Mock blueprint names based on our analysis
|
|
blueprint_names = [
|
|
('anime', 'anime.py'),
|
|
('episodes', 'episodes.py'),
|
|
('health_check', 'health.py'),
|
|
('auth', 'auth.py'),
|
|
('config', 'config.py'),
|
|
('scheduler', 'scheduler.py'),
|
|
('logging', 'logging.py'),
|
|
('storage', 'storage.py'),
|
|
('search', 'search.py'),
|
|
('downloads', 'downloads.py'),
|
|
('maintenance', 'maintenance.py'),
|
|
('performance', 'performance.py'),
|
|
('process', 'process.py'),
|
|
('integrations', 'integrations.py'),
|
|
('diagnostics', 'diagnostics.py'),
|
|
('database', 'database.py'),
|
|
('bulk_api', 'bulk.py'),
|
|
('backups', 'backups.py'),
|
|
]
|
|
|
|
return blueprint_names
|
|
|
|
def _find_duplicates(self, items: List[Tuple[str, str]]) -> List[str]:
|
|
"""
|
|
Find duplicate items in a list.
|
|
|
|
Args:
|
|
items: List of (name, source) tuples
|
|
|
|
Returns:
|
|
List of duplicate names
|
|
"""
|
|
seen = set()
|
|
duplicates = []
|
|
|
|
for name, source in items:
|
|
if name in seen:
|
|
duplicates.append(name)
|
|
seen.add(name)
|
|
|
|
return duplicates
|
|
|
|
|
|
class TestControllerStandardization:
|
|
"""Test suite for controller standardization compliance."""
|
|
|
|
def test_base_controller_usage(self):
|
|
"""
|
|
Test that controllers properly inherit from BaseController.
|
|
|
|
This would check that new controllers use the base controller
|
|
instead of implementing duplicate functionality.
|
|
"""
|
|
# This would scan controller files to ensure they inherit BaseController
|
|
# For now, this is a placeholder test
|
|
assert True # Placeholder
|
|
|
|
def test_shared_decorators_usage(self):
|
|
"""
|
|
Test that controllers use shared decorators instead of local implementations.
|
|
|
|
Ensures @handle_api_errors, @require_auth, etc. are imported
|
|
from shared modules rather than locally implemented.
|
|
"""
|
|
# This would scan for decorator usage patterns
|
|
# For now, this is a placeholder test
|
|
assert True # Placeholder
|
|
|
|
def test_response_format_consistency(self):
|
|
"""
|
|
Test that all endpoints return consistent response formats.
|
|
|
|
Ensures all responses follow the standardized format:
|
|
{"status": "success/error", "message": "...", "data": ...}
|
|
"""
|
|
# This would test actual endpoint responses
|
|
# For now, this is a placeholder test
|
|
assert True # Placeholder
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Run the tests
|
|
pytest.main([__file__, "-v"]) |