#!/usr/bin/env python3 # ================================================================ # services/auth/tests/run_tests.py # Complete test runner script for auth service with comprehensive reporting # ================================================================ """ Comprehensive test runner for authentication service Provides various test execution modes and detailed reporting """ import os import sys import subprocess import argparse import time import json from pathlib import Path from typing import List, Dict, Optional, Tuple from datetime import datetime # Add the project root to Python path project_root = Path(__file__).parent.parent.parent.parent sys.path.insert(0, str(project_root)) class Colors: """ANSI color codes for terminal output""" RED = '\033[91m' GREEN = '\033[92m' YELLOW = '\033[93m' BLUE = '\033[94m' MAGENTA = '\033[95m' CYAN = '\033[96m' WHITE = '\033[97m' BOLD = '\033[1m' UNDERLINE = '\033[4m' END = '\033[0m' @classmethod def colorize(cls, text: str, color: str) -> str: """Colorize text for terminal output""" return f"{color}{text}{cls.END}" class TestMetrics: """Track test execution metrics""" def __init__(self): self.start_time = None self.end_time = None self.tests_run = 0 self.tests_passed = 0 self.tests_failed = 0 self.tests_skipped = 0 self.coverage_percentage = 0.0 self.warnings_count = 0 self.errors = [] def start(self): """Start timing""" self.start_time = time.time() def stop(self): """Stop timing""" self.end_time = time.time() @property def duration(self) -> float: """Get duration in seconds""" if self.start_time and self.end_time: return self.end_time - self.start_time return 0.0 @property def success_rate(self) -> float: """Get success rate percentage""" if self.tests_run > 0: return (self.tests_passed / self.tests_run) * 100 return 0.0 class AuthTestRunner: """Test runner for authentication service with enhanced features""" def __init__(self, test_dir: str = "tests"): self.test_dir = Path(test_dir) self.project_root = Path(__file__).parent.parent self.results: Dict[str, TestMetrics] = {} self.overall_metrics = TestMetrics() def _print_header(self, title: str, char: str = "=", width: int = 80): """Print a formatted header""" print(Colors.colorize(char * width, Colors.CYAN)) centered_title = title.center(width) print(Colors.colorize(centered_title, Colors.BOLD + Colors.WHITE)) print(Colors.colorize(char * width, Colors.CYAN)) def _print_step(self, message: str, emoji: str = "๐Ÿ“‹"): """Print a step message""" print(f"\n{emoji} {Colors.colorize(message, Colors.BLUE)}") def _print_success(self, message: str): """Print success message""" print(f"โœ… {Colors.colorize(message, Colors.GREEN)}") def _print_error(self, message: str): """Print error message""" print(f"โŒ {Colors.colorize(message, Colors.RED)}") def _print_warning(self, message: str): """Print warning message""" print(f"โš ๏ธ {Colors.colorize(message, Colors.YELLOW)}") def run_command(self, cmd: List[str], capture_output: bool = True, timeout: int = 300) -> subprocess.CompletedProcess: """Run a command and return the result""" cmd_str = ' '.join(cmd) print(f"๐Ÿš€ Running: {Colors.colorize(cmd_str, Colors.MAGENTA)}") try: result = subprocess.run( cmd, capture_output=capture_output, text=True, cwd=self.project_root, timeout=timeout ) return result except subprocess.TimeoutExpired: self._print_error(f"Test execution timed out ({timeout} seconds)") return subprocess.CompletedProcess(cmd, 1, "", "Timeout") except Exception as e: self._print_error(f"Error running command: {e}") return subprocess.CompletedProcess(cmd, 1, "", str(e)) def _parse_pytest_output(self, output: str) -> TestMetrics: """Parse pytest output to extract metrics""" metrics = TestMetrics() lines = output.split('\n') for line in lines: line = line.strip() # Parse test results line (e.g., "45 passed, 2 failed, 1 skipped in 12.34s") if ' passed' in line or ' failed' in line: parts = line.split() for i, part in enumerate(parts): if part.isdigit(): count = int(part) if i + 1 < len(parts): result_type = parts[i + 1] if 'passed' in result_type: metrics.tests_passed = count elif 'failed' in result_type: metrics.tests_failed = count elif 'skipped' in result_type: metrics.tests_skipped = count elif 'warning' in result_type: metrics.warnings_count = count # Parse coverage percentage if 'TOTAL' in line and '%' in line: parts = line.split() for part in parts: if '%' in part: try: metrics.coverage_percentage = float(part.replace('%', '')) except ValueError: pass metrics.tests_run = metrics.tests_passed + metrics.tests_failed + metrics.tests_skipped return metrics def run_all_tests(self, verbose: bool = True) -> bool: """Run all authentication tests""" self._print_step("Running all authentication tests", "๐Ÿงช") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v" if verbose else "-q", "--tb=short", "--strict-markers", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=not verbose) metrics.stop() if not verbose and result.stdout: parsed_metrics = self._parse_pytest_output(result.stdout) metrics.tests_run = parsed_metrics.tests_run metrics.tests_passed = parsed_metrics.tests_passed metrics.tests_failed = parsed_metrics.tests_failed metrics.tests_skipped = parsed_metrics.tests_skipped self.results['all_tests'] = metrics success = result.returncode == 0 if success: self._print_success(f"All tests completed successfully ({metrics.duration:.2f}s)") else: self._print_error(f"Some tests failed ({metrics.duration:.2f}s)") return success def run_unit_tests(self) -> bool: """Run unit tests only""" self._print_step("Running unit tests", "๐Ÿ”ฌ") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v", "-m", "unit", "--tb=short", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=False) metrics.stop() self.results['unit_tests'] = metrics return result.returncode == 0 def run_integration_tests(self) -> bool: """Run integration tests only""" self._print_step("Running integration tests", "๐Ÿ”—") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v", "-m", "integration", "--tb=short", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=False) metrics.stop() self.results['integration_tests'] = metrics return result.returncode == 0 def run_api_tests(self) -> bool: """Run API endpoint tests only""" self._print_step("Running API tests", "๐ŸŒ") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v", "-m", "api", "--tb=short", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=False) metrics.stop() self.results['api_tests'] = metrics return result.returncode == 0 def run_security_tests(self) -> bool: """Run security tests only""" self._print_step("Running security tests", "๐Ÿ”’") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v", "-m", "security", "--tb=short", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=False) metrics.stop() self.results['security_tests'] = metrics return result.returncode == 0 def run_performance_tests(self) -> bool: """Run performance tests only""" self._print_step("Running performance tests", "โšก") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v", "-m", "performance", "--tb=short", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=False) metrics.stop() self.results['performance_tests'] = metrics return result.returncode == 0 def run_coverage_tests(self) -> bool: """Run tests with coverage reporting""" self._print_step("Running tests with coverage", "๐Ÿ“Š") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "--cov=app", "--cov-report=html:htmlcov", "--cov-report=term-missing", "--cov-report=xml", "--cov-branch", "-v", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=True) metrics.stop() if result.stdout: parsed_metrics = self._parse_pytest_output(result.stdout) metrics.coverage_percentage = parsed_metrics.coverage_percentage print(result.stdout) self.results['coverage_tests'] = metrics if result.returncode == 0: self._print_success("Coverage report generated in htmlcov/index.html") if metrics.coverage_percentage > 0: self._print_success(f"Coverage: {metrics.coverage_percentage:.1f}%") return result.returncode == 0 def run_fast_tests(self) -> bool: """Run fast tests (exclude slow/performance tests)""" self._print_step("Running fast tests only", "โšก") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v", "-m", "not slow", "--tb=short", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=False) metrics.stop() self.results['fast_tests'] = metrics return result.returncode == 0 def run_specific_test(self, test_pattern: str) -> bool: """Run specific test by pattern""" self._print_step(f"Running tests matching: {test_pattern}", "๐ŸŽฏ") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v", "-k", test_pattern, "--tb=short", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=False) metrics.stop() self.results[f'specific_test_{test_pattern}'] = metrics return result.returncode == 0 def run_parallel_tests(self, num_workers: Optional[int] = None) -> bool: """Run tests in parallel""" if num_workers is None: num_workers_str = "auto" else: num_workers_str = str(num_workers) self._print_step(f"Running tests in parallel with {num_workers_str} workers", "๐Ÿš€") cmd = [ sys.executable, "-m", "pytest", str(self.test_dir), "-v", "-n", num_workers_str, "--tb=short", "--color=yes" ] metrics = TestMetrics() metrics.start() result = self.run_command(cmd, capture_output=False) metrics.stop() self.results['parallel_tests'] = metrics return result.returncode == 0 def validate_test_environment(self) -> bool: """Validate that the test environment is set up correctly""" self._print_step("Validating test environment", "๐Ÿ”") validation_steps = [ ("Checking pytest availability", self._check_pytest), ("Checking test files", self._check_test_files), ("Checking app module", self._check_app_module), ("Checking database module", self._check_database_module), ("Checking dependencies", self._check_dependencies), ] all_valid = True for step_name, step_func in validation_steps: print(f" ๐Ÿ“‹ {step_name}...") if step_func(): self._print_success(f" {step_name}") else: self._print_error(f" {step_name}") all_valid = False return all_valid def _check_pytest(self) -> bool: """Check if pytest is available""" try: result = subprocess.run([sys.executable, "-m", "pytest", "--version"], capture_output=True, text=True) if result.returncode != 0: return False print(f" โœ… {result.stdout.strip()}") return True except Exception: return False def _check_test_files(self) -> bool: """Check if test files exist""" test_files = list(self.test_dir.glob("test_*.py")) if not test_files: print(f" โŒ No test files found in {self.test_dir}") return False print(f" โœ… Found {len(test_files)} test files") return True def _check_app_module(self) -> bool: """Check if app module can be imported""" try: sys.path.insert(0, str(self.project_root)) import app print(" โœ… App module can be imported") return True except ImportError as e: print(f" โŒ Cannot import app module: {e}") return False def _check_database_module(self) -> bool: """Check database connectivity""" try: from app.core.database import get_db print(" โœ… Database module available") return True except ImportError as e: print(f" โš ๏ธ Database module not available: {e}") return True # Non-critical for some tests def _check_dependencies(self) -> bool: """Check required dependencies""" required_packages = [ "pytest", "pytest-asyncio", "fastapi", "sqlalchemy", "pydantic" ] missing_packages = [] for package in required_packages: try: __import__(package.replace('-', '_')) except ImportError: missing_packages.append(package) if missing_packages: print(f" โŒ Missing packages: {', '.join(missing_packages)}") return False print(f" โœ… All required packages available") return True def generate_test_report(self) -> None: """Generate a comprehensive test report""" self._print_header("AUTH SERVICE TEST REPORT") if not self.results: print("No test results available") return # Summary table print(f"\n{Colors.colorize('Test Category', Colors.BOLD):<25} " f"{Colors.colorize('Status', Colors.BOLD):<12} " f"{Colors.colorize('Duration', Colors.BOLD):<12} " f"{Colors.colorize('Tests', Colors.BOLD):<15} " f"{Colors.colorize('Success Rate', Colors.BOLD):<12}") print("-" * 80) total_duration = 0 total_tests = 0 total_passed = 0 for test_type, metrics in self.results.items(): if metrics.duration > 0: total_duration += metrics.duration total_tests += metrics.tests_run total_passed += metrics.tests_passed # Status if metrics.tests_failed == 0 and metrics.tests_run > 0: status = Colors.colorize("โœ… PASSED", Colors.GREEN) elif metrics.tests_run == 0: status = Colors.colorize("โšช SKIPPED", Colors.YELLOW) else: status = Colors.colorize("โŒ FAILED", Colors.RED) # Duration duration_str = f"{metrics.duration:.2f}s" # Tests count if metrics.tests_run > 0: tests_str = f"{metrics.tests_passed}/{metrics.tests_run}" else: tests_str = "0" # Success rate if metrics.tests_run > 0: success_rate_str = f"{metrics.success_rate:.1f}%" else: success_rate_str = "N/A" print(f"{test_type.replace('_', ' ').title():<25} " f"{status:<20} " f"{duration_str:<12} " f"{tests_str:<15} " f"{success_rate_str:<12}") # Overall summary print("-" * 80) overall_success_rate = (total_passed / total_tests * 100) if total_tests > 0 else 0 overall_status = "โœ… PASSED" if total_passed == total_tests and total_tests > 0 else "โŒ FAILED" print(f"{'OVERALL':<25} " f"{Colors.colorize(overall_status, Colors.BOLD):<20} " f"{total_duration:.2f}s{'':<6} " f"{total_passed}/{total_tests}{'':11} " f"{overall_success_rate:.1f}%") print("\n" + "=" * 80) # Recommendations self._print_recommendations(overall_success_rate, total_tests) def _print_recommendations(self, success_rate: float, total_tests: int): """Print recommendations based on test results""" print(f"\n{Colors.colorize('๐Ÿ“‹ RECOMMENDATIONS', Colors.BOLD + Colors.CYAN)}") if success_rate == 100 and total_tests > 0: self._print_success("Excellent! All tests passed. Your auth service is ready for deployment.") elif success_rate >= 90: self._print_warning("Good test coverage. Review failed tests before deployment.") elif success_rate >= 70: self._print_warning("Moderate test coverage. Significant issues need fixing.") else: self._print_error("Poor test results. Major issues need addressing before deployment.") # Specific recommendations recommendations = [] if 'security_tests' in self.results: security_metrics = self.results['security_tests'] if security_metrics.tests_failed > 0: recommendations.append("๐Ÿ”’ Fix security test failures - critical for production") if 'coverage_tests' in self.results: coverage_metrics = self.results['coverage_tests'] if coverage_metrics.coverage_percentage < 80: recommendations.append(f"๐Ÿ“Š Increase test coverage (current: {coverage_metrics.coverage_percentage:.1f}%)") if 'performance_tests' in self.results: perf_metrics = self.results['performance_tests'] if perf_metrics.tests_failed > 0: recommendations.append("โšก Address performance issues") if recommendations: print("\n" + Colors.colorize("Next Steps:", Colors.BOLD)) for i, rec in enumerate(recommendations, 1): print(f" {i}. {rec}") def clean_test_artifacts(self) -> None: """Clean up test artifacts""" self._print_step("Cleaning test artifacts", "๐Ÿงน") artifacts = [ ".pytest_cache", "htmlcov", ".coverage", "coverage.xml", "report.html", "test-results.xml" ] cleaned_count = 0 for artifact in artifacts: artifact_path = self.project_root / artifact if artifact_path.exists(): if artifact_path.is_dir(): import shutil shutil.rmtree(artifact_path) else: artifact_path.unlink() self._print_success(f"Removed {artifact}") cleaned_count += 1 # Clean __pycache__ directories pycache_count = 0 for pycache in self.project_root.rglob("__pycache__"): import shutil shutil.rmtree(pycache) pycache_count += 1 # Clean .pyc files pyc_count = 0 for pyc in self.project_root.rglob("*.pyc"): pyc.unlink() pyc_count += 1 if pycache_count > 0: self._print_success(f"Removed {pycache_count} __pycache__ directories") if pyc_count > 0: self._print_success(f"Removed {pyc_count} .pyc files") if cleaned_count == 0 and pycache_count == 0 and pyc_count == 0: print(" ๐Ÿ“ No artifacts to clean") else: self._print_success("Test artifacts cleaned successfully") def save_results_json(self, filename: str = "test_results.json") -> None: """Save test results to JSON file""" results_data = { "timestamp": datetime.now().isoformat(), "test_categories": {} } for test_type, metrics in self.results.items(): results_data["test_categories"][test_type] = { "duration": metrics.duration, "tests_run": metrics.tests_run, "tests_passed": metrics.tests_passed, "tests_failed": metrics.tests_failed, "tests_skipped": metrics.tests_skipped, "success_rate": metrics.success_rate, "coverage_percentage": metrics.coverage_percentage, "warnings_count": metrics.warnings_count } with open(filename, 'w') as f: json.dump(results_data, f, indent=2) self._print_success(f"Test results saved to {filename}") def main(): """Main entry point for test runner""" parser = argparse.ArgumentParser( description="Auth Service Test Runner", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python run_tests.py # Run all tests python run_tests.py --test-type security # Run security tests only python run_tests.py --coverage # Run with coverage python run_tests.py --parallel --workers 4 # Run in parallel python run_tests.py --pattern "test_login" # Run specific test pattern python run_tests.py --validate # Validate environment python run_tests.py --clean # Clean test artifacts """ ) parser.add_argument("--test-type", choices=["all", "unit", "integration", "api", "security", "performance", "fast"], default="all", help="Type of tests to run") parser.add_argument("--coverage", action="store_true", help="Run with coverage") parser.add_argument("--parallel", action="store_true", help="Run tests in parallel") parser.add_argument("--workers", type=int, help="Number of parallel workers") parser.add_argument("--pattern", type=str, help="Run specific test pattern") parser.add_argument("--validate", action="store_true", help="Validate test environment") parser.add_argument("--clean", action="store_true", help="Clean test artifacts") parser.add_argument("--verbose", action="store_true", default=True, help="Verbose output") parser.add_argument("--save-results", action="store_true", help="Save results to JSON file") parser.add_argument("--quiet", action="store_true", help="Quiet mode (less output)") args = parser.parse_args() runner = AuthTestRunner() # Print header if not args.quiet: runner._print_header("๐Ÿงช AUTH SERVICE TEST RUNNER ๐Ÿงช") # Clean artifacts if requested if args.clean: runner.clean_test_artifacts() return # Validate environment if requested if args.validate: success = runner.validate_test_environment() if success: runner._print_success("Test environment validation passed") else: runner._print_error("Test environment validation failed") sys.exit(0 if success else 1) # Validate environment before running tests if not args.quiet: if not runner.validate_test_environment(): runner._print_error("Test environment validation failed") sys.exit(1) success = True try: runner.overall_metrics.start() if args.pattern: success = runner.run_specific_test(args.pattern) elif args.coverage: success = runner.run_coverage_tests() elif args.parallel: success = runner.run_parallel_tests(args.workers) elif args.test_type == "unit": success = runner.run_unit_tests() elif args.test_type == "integration": success = runner.run_integration_tests() elif args.test_type == "api": success = runner.run_api_tests() elif args.test_type == "security": success = runner.run_security_tests() elif args.test_type == "performance": success = runner.run_performance_tests() elif args.test_type == "fast": success = runner.run_fast_tests() else: # all success = runner.run_all_tests(args.verbose) runner.overall_metrics.stop() if not args.quiet: runner.generate_test_report() if args.save_results: runner.save_results_json() except KeyboardInterrupt: runner._print_error("Tests interrupted by user") success = False except Exception as e: runner._print_error(f"Error running tests: {e}") success = False if success: if not args.quiet: runner._print_success("All tests completed successfully!") sys.exit(0) else: if not args.quiet: runner._print_error("Some tests failed!") sys.exit(1) if __name__ == "__main__": main()