611 Zeilen
22 KiB
Python
611 Zeilen
22 KiB
Python
"""
|
|
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) |