""" Comprehensive tests for the method rotation system. Tests all components: entities, repositories, use cases, and integration. """ import unittest import os import sys import tempfile import sqlite3 from datetime import datetime, timedelta from unittest.mock import Mock, patch, MagicMock # Add project root to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) from domain.entities.method_rotation import ( MethodStrategy, RotationSession, RotationEvent, PlatformMethodState, RiskLevel, RotationEventType, RotationStrategy ) from application.use_cases.method_rotation_use_case import MethodRotationUseCase, RotationContext from infrastructure.repositories.method_strategy_repository import MethodStrategyRepository from infrastructure.repositories.rotation_session_repository import RotationSessionRepository from infrastructure.repositories.platform_method_state_repository import PlatformMethodStateRepository class MockDBManager: """Mock database manager for testing""" def __init__(self): self.db_path = tempfile.mktemp(suffix='.db') self.connection = None self._setup_test_database() def _setup_test_database(self): """Create test database with rotation tables""" conn = sqlite3.connect(self.db_path) # Create rotation system tables with open('database/migrations/add_method_rotation_system.sql', 'r') as f: sql_script = f.read() # Remove the INSERT statements for tests sql_lines = sql_script.split('\n') create_statements = [line for line in sql_lines if line.strip() and not line.strip().startswith('INSERT')] clean_sql = '\n'.join(create_statements) conn.executescript(clean_sql) conn.close() def get_connection(self): if not self.connection: self.connection = sqlite3.connect(self.db_path) return self.connection def execute_query(self, query, params=None): conn = self.get_connection() cursor = conn.cursor() if params: cursor.execute(query, params) else: cursor.execute(query) conn.commit() return cursor def fetch_one(self, query, params=None): conn = self.get_connection() cursor = conn.cursor() if params: cursor.execute(query, params) else: cursor.execute(query) return cursor.fetchone() def fetch_all(self, query, params=None): conn = self.get_connection() cursor = conn.cursor() if params: cursor.execute(query, params) else: cursor.execute(query) return cursor.fetchall() def close(self): if self.connection: self.connection.close() if os.path.exists(self.db_path): os.unlink(self.db_path) class TestMethodStrategy(unittest.TestCase): """Test MethodStrategy entity""" def test_method_strategy_creation(self): """Test creating a method strategy""" strategy = MethodStrategy( strategy_id="test_id", platform="instagram", method_name="email", priority=8, risk_level=RiskLevel.LOW ) self.assertEqual(strategy.strategy_id, "test_id") self.assertEqual(strategy.platform, "instagram") self.assertEqual(strategy.method_name, "email") self.assertEqual(strategy.priority, 8) self.assertEqual(strategy.risk_level, RiskLevel.LOW) self.assertTrue(strategy.is_active) def test_effectiveness_score_calculation(self): """Test effectiveness score calculation""" strategy = MethodStrategy( strategy_id="test_id", platform="instagram", method_name="email", priority=8, success_rate=0.9, failure_rate=0.1, risk_level=RiskLevel.LOW ) score = strategy.effectiveness_score self.assertGreater(score, 0.8) # High priority, high success rate should score well def test_cooldown_functionality(self): """Test cooldown period functionality""" strategy = MethodStrategy( strategy_id="test_id", platform="instagram", method_name="email", cooldown_period=300, last_failure=datetime.now() - timedelta(seconds=100) ) self.assertTrue(strategy.is_on_cooldown) self.assertGreater(strategy.cooldown_remaining_seconds, 0) # Test expired cooldown strategy.last_failure = datetime.now() - timedelta(seconds=400) self.assertFalse(strategy.is_on_cooldown) def test_performance_update(self): """Test performance metrics update""" strategy = MethodStrategy( strategy_id="test_id", platform="instagram", method_name="email", success_rate=0.5, failure_rate=0.5 ) # Update with success strategy.update_performance(True, 120.0) self.assertGreater(strategy.success_rate, 0.5) self.assertLess(strategy.failure_rate, 0.5) self.assertIsNotNone(strategy.last_success) # Update with failure original_success_rate = strategy.success_rate strategy.update_performance(False) self.assertLess(strategy.success_rate, original_success_rate) self.assertIsNotNone(strategy.last_failure) class TestRotationSession(unittest.TestCase): """Test RotationSession entity""" def test_rotation_session_creation(self): """Test creating a rotation session""" session = RotationSession( session_id="test_session", platform="instagram", current_method="email" ) self.assertEqual(session.session_id, "test_session") self.assertEqual(session.platform, "instagram") self.assertEqual(session.current_method, "email") self.assertTrue(session.is_active) self.assertEqual(session.rotation_count, 0) def test_session_metrics(self): """Test session metrics calculation""" session = RotationSession( session_id="test_session", platform="instagram", current_method="email" ) # Add some attempts session.add_attempt("email", True) session.add_attempt("email", False) session.add_attempt("phone", True) self.assertEqual(session.success_count, 2) self.assertEqual(session.failure_count, 1) self.assertAlmostEqual(session.success_rate, 2/3, places=2) def test_rotation_logic(self): """Test rotation decision logic""" session = RotationSession( session_id="test_session", platform="instagram", current_method="email" ) # Add failures to trigger rotation session.add_attempt("email", False) session.add_attempt("email", False) self.assertTrue(session.should_rotate) # Test rotation session.rotate_to_method("phone", "consecutive_failures") self.assertEqual(session.current_method, "phone") self.assertEqual(session.rotation_count, 1) self.assertEqual(session.rotation_reason, "consecutive_failures") class TestMethodStrategyRepository(unittest.TestCase): """Test MethodStrategyRepository""" def setUp(self): self.db_manager = MockDBManager() self.repo = MethodStrategyRepository(self.db_manager) def tearDown(self): self.db_manager.close() def test_save_and_find_strategy(self): """Test saving and finding strategies""" strategy = MethodStrategy( strategy_id="test_strategy", platform="instagram", method_name="email", priority=8, risk_level=RiskLevel.LOW ) # Save strategy self.repo.save(strategy) # Find by ID found_strategy = self.repo.find_by_id("test_strategy") self.assertIsNotNone(found_strategy) self.assertEqual(found_strategy.strategy_id, "test_strategy") self.assertEqual(found_strategy.platform, "instagram") self.assertEqual(found_strategy.method_name, "email") def test_find_active_by_platform(self): """Test finding active strategies by platform""" # Create multiple strategies strategies = [ MethodStrategy("s1", "instagram", "email", 8, risk_level=RiskLevel.LOW, success_rate=0.9), MethodStrategy("s2", "instagram", "phone", 6, risk_level=RiskLevel.MEDIUM, success_rate=0.7), MethodStrategy("s3", "instagram", "social", 4, risk_level=RiskLevel.HIGH, success_rate=0.3, is_active=False), MethodStrategy("s4", "tiktok", "email", 8, risk_level=RiskLevel.LOW, success_rate=0.8) ] for strategy in strategies: self.repo.save(strategy) # Find active Instagram strategies active_strategies = self.repo.find_active_by_platform("instagram") self.assertEqual(len(active_strategies), 2) # Only active ones self.assertEqual(active_strategies[0].method_name, "email") # Highest effectiveness def test_get_next_available_method(self): """Test getting next available method""" # Create strategies strategies = [ MethodStrategy("s1", "instagram", "email", 8, risk_level=RiskLevel.LOW, success_rate=0.9), MethodStrategy("s2", "instagram", "phone", 6, risk_level=RiskLevel.MEDIUM, success_rate=0.7), ] for strategy in strategies: self.repo.save(strategy) # Get next method excluding email next_method = self.repo.get_next_available_method("instagram", ["email"]) self.assertIsNotNone(next_method) self.assertEqual(next_method.method_name, "phone") # Get next method with no exclusions best_method = self.repo.get_next_available_method("instagram") self.assertIsNotNone(best_method) self.assertEqual(best_method.method_name, "email") # Best strategy def test_platform_statistics(self): """Test platform statistics calculation""" # Create strategies with different metrics strategies = [ MethodStrategy("s1", "instagram", "email", 8, risk_level=RiskLevel.LOW, success_rate=0.9, last_success=datetime.now()), MethodStrategy("s2", "instagram", "phone", 6, risk_level=RiskLevel.MEDIUM, success_rate=0.6, last_failure=datetime.now()), ] for strategy in strategies: self.repo.save(strategy) stats = self.repo.get_platform_statistics("instagram") self.assertEqual(stats['total_methods'], 2) self.assertEqual(stats['active_methods'], 2) self.assertGreater(stats['avg_success_rate'], 0) self.assertEqual(stats['recent_successes_24h'], 1) class TestRotationUseCase(unittest.TestCase): """Test MethodRotationUseCase""" def setUp(self): self.db_manager = MockDBManager() self.strategy_repo = MethodStrategyRepository(self.db_manager) self.session_repo = RotationSessionRepository(self.db_manager) self.state_repo = PlatformMethodStateRepository(self.db_manager) self.use_case = MethodRotationUseCase( self.strategy_repo, self.session_repo, self.state_repo ) # Setup test data self._setup_test_strategies() def tearDown(self): self.db_manager.close() def _setup_test_strategies(self): """Setup test strategies""" strategies = [ MethodStrategy("instagram_email", "instagram", "email", 8, risk_level=RiskLevel.LOW, success_rate=0.9), MethodStrategy("instagram_phone", "instagram", "phone", 6, risk_level=RiskLevel.MEDIUM, success_rate=0.7), MethodStrategy("tiktok_email", "tiktok", "email", 8, risk_level=RiskLevel.LOW, success_rate=0.8), ] for strategy in strategies: self.strategy_repo.save(strategy) def test_start_rotation_session(self): """Test starting a rotation session""" context = RotationContext( platform="instagram", account_id="test_account" ) session = self.use_case.start_rotation_session(context) self.assertIsNotNone(session) self.assertEqual(session.platform, "instagram") self.assertEqual(session.current_method, "email") # Best method self.assertTrue(session.is_active) def test_get_optimal_method(self): """Test getting optimal method""" context = RotationContext(platform="instagram") method = self.use_case.get_optimal_method(context) self.assertIsNotNone(method) self.assertEqual(method.method_name, "email") # Best strategy # Test with exclusions context.excluded_methods = ["email"] method = self.use_case.get_optimal_method(context) self.assertEqual(method.method_name, "phone") def test_method_rotation(self): """Test method rotation""" # Start session context = RotationContext(platform="instagram") session = self.use_case.start_rotation_session(context) # Record failure to trigger rotation self.use_case.record_method_result( session.session_id, "email", False, 0.0, {'error_type': 'rate_limit', 'message': 'Rate limited'} ) # Check if rotation should occur should_rotate = self.use_case.should_rotate_method(session.session_id) if should_rotate: # Attempt rotation next_method = self.use_case.rotate_method(session.session_id, "rate_limit") self.assertIsNotNone(next_method) self.assertEqual(next_method.method_name, "phone") def test_emergency_mode(self): """Test emergency mode functionality""" # Enable emergency mode self.use_case.enable_emergency_mode("instagram", "test_emergency") # Check that platform state reflects emergency mode state = self.state_repo.find_by_platform("instagram") self.assertTrue(state.emergency_mode) # Disable emergency mode self.use_case.disable_emergency_mode("instagram") state = self.state_repo.find_by_platform("instagram") self.assertFalse(state.emergency_mode) def test_performance_tracking(self): """Test performance tracking and metrics""" context = RotationContext(platform="instagram") session = self.use_case.start_rotation_session(context) # Record success self.use_case.record_method_result( session.session_id, "email", True, 120.0 ) # Get recommendations recommendations = self.use_case.get_platform_method_recommendations("instagram") self.assertIn('platform', recommendations) self.assertIn('recommended_methods', recommendations) self.assertGreater(len(recommendations['recommended_methods']), 0) class TestIntegration(unittest.TestCase): """Integration tests for the complete rotation system""" def setUp(self): self.db_manager = MockDBManager() def tearDown(self): self.db_manager.close() def test_complete_rotation_workflow(self): """Test complete rotation workflow from start to finish""" # Initialize components strategy_repo = MethodStrategyRepository(self.db_manager) session_repo = RotationSessionRepository(self.db_manager) state_repo = PlatformMethodStateRepository(self.db_manager) use_case = MethodRotationUseCase(strategy_repo, session_repo, state_repo) # Setup strategies strategies = [ MethodStrategy("instagram_email", "instagram", "email", 8, risk_level=RiskLevel.LOW, success_rate=0.9, max_daily_attempts=20), MethodStrategy("instagram_phone", "instagram", "phone", 6, risk_level=RiskLevel.MEDIUM, success_rate=0.7, max_daily_attempts=10), ] for strategy in strategies: strategy_repo.save(strategy) # 1. Start rotation session context = RotationContext(platform="instagram", account_id="test_account") session = use_case.start_rotation_session(context) self.assertIsNotNone(session) self.assertEqual(session.current_method, "email") # 2. Simulate failure and rotation use_case.record_method_result( session.session_id, "email", False, 0.0, {'error_type': 'rate_limit', 'message': 'Rate limited'} ) # Check rotation trigger if use_case.should_rotate_method(session.session_id): next_method = use_case.rotate_method(session.session_id, "rate_limit") self.assertEqual(next_method.method_name, "phone") # 3. Simulate success with new method use_case.record_method_result( session.session_id, "phone", True, 180.0 ) # 4. Verify session is completed session_status = use_case.get_session_status(session.session_id) self.assertIsNotNone(session_status) def test_error_handling_and_fallback(self): """Test error handling and fallback mechanisms""" # Test with invalid platform strategy_repo = MethodStrategyRepository(self.db_manager) session_repo = RotationSessionRepository(self.db_manager) state_repo = PlatformMethodStateRepository(self.db_manager) use_case = MethodRotationUseCase(strategy_repo, session_repo, state_repo) # Try to get method for platform with no strategies context = RotationContext(platform="nonexistent") method = use_case.get_optimal_method(context) self.assertIsNone(method) # Should handle gracefully def test_concurrent_sessions(self): """Test handling multiple concurrent sessions""" strategy_repo = MethodStrategyRepository(self.db_manager) session_repo = RotationSessionRepository(self.db_manager) state_repo = PlatformMethodStateRepository(self.db_manager) use_case = MethodRotationUseCase(strategy_repo, session_repo, state_repo) # Setup strategy strategy = MethodStrategy("instagram_email", "instagram", "email", 8, risk_level=RiskLevel.LOW, success_rate=0.9) strategy_repo.save(strategy) # Start multiple sessions sessions = [] for i in range(3): context = RotationContext(platform="instagram", account_id=f"account_{i}") session = use_case.start_rotation_session(context) sessions.append(session) # Verify all sessions are active and distinct self.assertEqual(len(sessions), 3) session_ids = [s.session_id for s in sessions] self.assertEqual(len(set(session_ids)), 3) # All unique class TestMixinIntegration(unittest.TestCase): """Test mixin integration with controllers""" def test_controller_mixin_integration(self): """Test that controller mixins work correctly""" from controllers.platform_controllers.method_rotation_mixin import MethodRotationMixin # Create mock controller with mixin class MockController(MethodRotationMixin): def __init__(self): self.platform_name = "instagram" self.db_manager = MockDBManager() self.logger = Mock() self._init_method_rotation_system() controller = MockController() # Test that rotation system is initialized self.assertIsNotNone(controller.method_rotation_use_case) # Test availability check self.assertTrue(controller._should_use_rotation_system()) # Cleanup controller.db_manager.close() def test_worker_mixin_integration(self): """Test worker thread mixin integration""" from controllers.platform_controllers.method_rotation_worker_mixin import MethodRotationWorkerMixin # Create mock worker with mixin class MockWorker(MethodRotationWorkerMixin): def __init__(self): self.params = {'registration_method': 'email'} self.log_signal = Mock() self.rotation_retry_count = 0 self.max_rotation_retries = 3 self.controller_instance = None worker = MockWorker() # Test initialization worker._init_rotation_support() # Test availability check available = worker._is_rotation_available() self.assertFalse(available) # No controller instance # Test error classification error_type = worker._classify_error("Rate limit exceeded") self.assertEqual(error_type, "rate_limit") if __name__ == '__main__': # Create test suite test_suite = unittest.TestSuite() # Add test cases test_cases = [ TestMethodStrategy, TestRotationSession, TestMethodStrategyRepository, TestRotationUseCase, TestIntegration, TestMixinIntegration ] for test_case in test_cases: tests = unittest.TestLoader().loadTestsFromTestCase(test_case) test_suite.addTests(tests) # Run tests runner = unittest.TextTestRunner(verbosity=2) result = runner.run(test_suite) # Print summary print(f"\nTest Summary:") print(f"Tests run: {result.testsRun}") print(f"Failures: {len(result.failures)}") print(f"Errors: {len(result.errors)}") if result.failures: print("\nFailures:") for test, traceback in result.failures: print(f"- {test}: {traceback}") if result.errors: print("\nErrors:") for test, traceback in result.errors: print(f"- {test}: {traceback}") # Exit with appropriate code sys.exit(0 if result.wasSuccessful() else 1)