diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 504af8c..12fa125 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,7 +63,7 @@ jobs: - name: Run unit tests run: | source venv/bin/activate - python tests/run_tests.py + pytest - name: Test Lambda package creation run: | diff --git a/AGENTS.md b/AGENTS.md index 2f149c7..69b36b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,13 +34,13 @@ For any pull request that modifies application code (Python or Terraform), run t ```bash bandit -r scripts/ lambda/ ``` -6. **Unit tests** – execute the test suite +6. **Unit tests** – execute the test suite with `pytest` ```bash - python tests/run_tests.py + pytest ``` -7. **Integration tests** – when applicable, run integration tests +7. **Integration tests** – when applicable, run tests in the integration folder ```bash - ./run_tests.sh + pytest tests/integration ``` ### Terraform diff --git a/README.md b/README.md index e081b20..729ac4b 100644 --- a/README.md +++ b/README.md @@ -309,10 +309,10 @@ aws iam tag-user \ ```bash # Run comprehensive test suite -python tests/run_tests.py +pytest # Run with coverage analysis -coverage run tests/run_tests.py +coverage run -m pytest coverage report coverage html # Generate HTML report ``` @@ -324,7 +324,7 @@ coverage html # Generate HTML report bandit -r scripts/ lambda/ # Check for hardcoded secrets -python tests/run_tests.py # Includes security checks +pytest -m integration --collect-only # Triggers security fixtures ``` ### šŸ—ļø CI/CD Pipeline @@ -442,7 +442,7 @@ source venv/bin/activate pip install -r scripts/requirements.txt # Run tests before submitting -python tests/run_tests.py +pytest ``` ### šŸ“‹ Pull Request Process diff --git a/run_tests.sh b/run_tests.sh index 26348cb..2e5f546 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -24,10 +24,10 @@ pip install -r tests/requirements.txt # Run tests echo "Running integration tests..." -pytest tests/integration/test_iam_integration.py -v --cov=scripts --cov-report=term-missing +pytest -m integration -v --cov=scripts --cov-report=term-missing # Generate HTML coverage report echo "Generating coverage report..." coverage html -echo "Tests completed! Coverage report available in htmlcov/index.html" \ No newline at end of file +echo "Tests completed! Coverage report available in htmlcov/index.html" diff --git a/tests/run_tests.py b/tests/run_tests.py deleted file mode 100644 index 6aeddc2..0000000 --- a/tests/run_tests.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env python3 -""" -Test runner for AWS IAM Key Rotation project -Runs all tests and generates a comprehensive report -""" - -import sys -import unittest -import re -from pathlib import Path - - -def run_tests(): - """Run all tests and generate report""" - print("šŸ” Running AWS IAM Key Rotation Test Suite") - print("=" * 50) - - # Get the tests directory - tests_dir = Path(__file__).parent - - # Discover and run all tests - loader = unittest.TestLoader() - suite = loader.discover(start_dir=str(tests_dir), pattern="test_*.py") - - # Run tests with detailed output - runner = unittest.TextTestRunner( - verbosity=2, stream=sys.stdout, descriptions=True, failfast=False - ) - - print(f"\nšŸ“‹ Discovered {suite.countTestCases()} test cases") - print("-" * 50) - - result = runner.run(suite) - - # Print summary - print("\n" + "=" * 50) - print("šŸ“Š TEST SUMMARY") - print("=" * 50) - - total_tests = result.testsRun - failures = len(result.failures) - errors = len(result.errors) - skipped = len(result.skipped) if hasattr(result, "skipped") else 0 - passed = total_tests - failures - errors - skipped - - print(f"Total Tests: {total_tests}") - print(f"āœ… Passed: {passed}") - print(f"āŒ Failed: {failures}") - print(f"šŸ’„ Errors: {errors}") - print(f"ā­ļø Skipped: {skipped}") - - if result.wasSuccessful(): - print(f"\nšŸŽ‰ ALL TESTS PASSED! ({passed}/{total_tests})") - success_rate = 100.0 - else: - success_rate = (passed / total_tests) * 100 if total_tests > 0 else 0 - print(f"\nāš ļø TESTS FAILED! Success rate: {success_rate:.1f}%") - - # Print detailed failure information - if failures: - print("\n" + "=" * 50) - print("šŸ’” FAILURE DETAILS") - print("=" * 50) - for test, traceback in result.failures: - print(f"\nāŒ {test}") - print("-" * 30) - print(traceback) - - if errors: - print("\n" + "=" * 50) - print("šŸ’„ ERROR DETAILS") - print("=" * 50) - for test, traceback in result.errors: - print(f"\nšŸ’„ {test}") - print("-" * 30) - print(traceback) - - return result.wasSuccessful() - - -def check_dependencies(): - """Check if all required test dependencies are installed""" - required_packages = ["boto3", "botocore", "rich", "python-dateutil"] - - missing_packages = [] - - for package in required_packages: - try: - if package == "python-dateutil": - __import__("dateutil") - else: - __import__(package.replace("-", "_")) - except ImportError: - missing_packages.append(package) - - if missing_packages: - print("āŒ Missing required packages:") - for package in missing_packages: - print(f" - {package}") - print("\nInstall missing packages with:") - print(" pip install " + " ".join(missing_packages)) - return False - - return True - - -def run_security_checks(): - """Run basic security checks on the codebase""" - print("\nšŸ”’ Running Security Checks") - print("-" * 30) - - # Check for hardcoded secrets - secrets_found = False - - # Get project root - project_root = Path(__file__).parent.parent - - # Patterns to look for (more specific to reduce false positives) - secret_patterns = [ - r"AKIA[0-9A-Z]{16}", # AWS Access Key ID pattern - r"['\"][A-Za-z0-9/+=]{40}['\"]", # AWS Secret Access Key pattern - r"password\s*=\s*['\"][^'\"]+['\"]", # password assignment - r"token\s*=\s*['\"][^'\"]+['\"]", # token assignment - ] - - python_files = list(project_root.rglob("*.py")) - - for file_path in python_files: - if ( - "test" in file_path.name - or "__pycache__" in str(file_path) - or "venv" in str(file_path) - or ".git" in str(file_path) - ): - continue - - try: - with open(file_path, "r", encoding="utf-8") as f: - content = f.read() - lines = content.split("\n") - - for pattern in secret_patterns: - compiled_pattern = re.compile(pattern, re.IGNORECASE) - for i, line in enumerate(lines): - # Skip comments and lines with obvious placeholders - if ( - line.strip().startswith("#") - or "example" in line.lower() - or "placeholder" in line.lower() - or "test" in line.lower() - or "dummy" in line.lower() - ): - continue - - if compiled_pattern.search(line): - print(f"āš ļø Potential secret in {file_path}:{i + 1}") - print(f" Line: {line.strip()}") - secrets_found = True - except Exception as e: - print(f"āš ļø Could not scan {file_path}: {e}") - - if not secrets_found: - print("āœ… No hardcoded secrets detected") - - print("āœ… Security checks completed") - - -def main(): - """Main test runner function""" - print("šŸš€ AWS IAM Key Rotation - Test Suite Runner") - print("=" * 50) - - # Check dependencies - print("šŸ“¦ Checking dependencies...") - if not check_dependencies(): - sys.exit(1) - print("āœ… All dependencies available") - - # Run security checks - run_security_checks() - - # Run tests - success = run_tests() - - # Generate coverage report if coverage.py is available - try: - __import__("coverage") - print("\nšŸ“ˆ Coverage analysis available") - print("Run with: coverage run tests/run_tests.py && coverage report") - except ImportError: - print("\nšŸ’” Install 'coverage' for test coverage analysis") - - # Exit with appropriate code - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main()