new folder structure

This commit is contained in:
2025-09-29 09:17:13 +02:00
parent 38117ab875
commit 78fc6068fb
197 changed files with 3490 additions and 1117 deletions

View File

@@ -0,0 +1,593 @@
"""
Unit Tests for Core Functionality
This module contains unit tests for the core components of the AniWorld application,
including series management, download operations, and API functionality.
"""
import unittest
import os
import sys
import tempfile
import shutil
import sqlite3
import json
from unittest.mock import Mock, MagicMock, patch, call
from datetime import datetime, timedelta
import threading
# Add parent directory to path for imports
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
# Import core modules
from src.server.core.entities.series import Serie
from src.server.core.entities.SerieList import SerieList
from src.server.infrastructure.file_system.SerieScanner import SerieScanner
# TODO: Fix imports - these modules may not exist or may be in different locations
# from database_manager import DatabaseManager, AnimeMetadata, EpisodeMetadata, BackupManager
# from error_handler import ErrorRecoveryManager, RetryMechanism, NetworkHealthChecker
# from performance_optimizer import SpeedLimiter, DownloadCache, MemoryMonitor
# from api_integration import WebhookManager, ExportManager
class TestSerie(unittest.TestCase):
"""Test cases for Serie class."""
def setUp(self):
"""Set up test fixtures."""
self.test_key = "test-key"
self.test_name = "Test Anime"
self.test_site = "test-site"
self.test_folder = "test_folder"
self.test_episodes = {1: [1], 2: [2]}
def test_serie_initialization(self):
"""Test Serie object initialization."""
serie = Serie(self.test_key, self.test_name, self.test_site, self.test_folder, self.test_episodes)
self.assertEqual(serie.key, self.test_key)
self.assertEqual(serie.name, self.test_name)
self.assertEqual(serie.site, self.test_site)
self.assertEqual(serie.folder, self.test_folder)
self.assertEqual(serie.episodeDict, self.test_episodes)
def test_serie_str_representation(self):
"""Test string representation of Serie."""
serie = Serie(self.test_key, self.test_name, self.test_site, self.test_folder, self.test_episodes)
str_repr = str(serie)
self.assertIn(self.test_name, str_repr)
self.assertIn(self.test_folder, str_repr)
self.assertIn(self.test_key, str_repr)
def test_serie_episode_management(self):
"""Test episode dictionary management."""
serie = Serie(self.test_key, self.test_name, self.test_site, self.test_folder, self.test_episodes)
# Test episode dict
self.assertEqual(len(serie.episodeDict), 2)
self.assertIn(1, serie.episodeDict)
self.assertIn(2, serie.episodeDict)
def test_serie_equality(self):
"""Test Serie equality comparison."""
serie1 = Serie(self.test_key, self.test_name, self.test_site, self.test_folder, self.test_episodes)
serie2 = Serie(self.test_key, self.test_name, self.test_site, self.test_folder, self.test_episodes)
serie3 = Serie("different-key", "Different", self.test_site, self.test_folder, self.test_episodes)
# Should be equal based on key attributes
self.assertEqual(serie1.key, serie2.key)
self.assertEqual(serie1.folder, serie2.folder)
self.assertNotEqual(serie1.key, serie3.key)
class TestSeriesList(unittest.TestCase):
"""Test cases for SeriesList class."""
def setUp(self):
"""Set up test fixtures."""
self.temp_dir = tempfile.mkdtemp()
self.series_list = SerieList(self.temp_dir)
def tearDown(self):
"""Clean up test fixtures."""
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_series_list_initialization(self):
"""Test SerieList initialization."""
self.assertIsInstance(self.series_list.folderDict, dict)
self.assertEqual(len(self.series_list.folderDict), 0)
def test_add_serie_to_list(self):
"""Test adding serie to list."""
serie = Serie("test-key", "Test", "test-site", "test_folder", {})
self.series_list.add(serie)
self.assertEqual(len(self.series_list.folderDict), 1)
self.assertIn("test_folder", self.series_list.folderDict)
def test_contains_serie(self):
"""Test checking if serie exists."""
serie = Serie("test-key", "Test", "test-site", "test_folder", {})
self.series_list.add(serie)
self.assertTrue(self.series_list.contains("test-key"))
self.assertFalse(self.series_list.contains("nonexistent"))
def test_get_series_with_missing_episodes(self):
"""Test filtering series with missing episodes."""
serie1 = Serie("key1", "Anime 1", "test-site", "folder1", {1: [1], 2: [2]}) # Has missing episodes
serie2 = Serie("key2", "Anime 2", "test-site", "folder2", {}) # No missing episodes
self.series_list.add(serie1)
self.series_list.add(serie2)
missing = self.series_list.GetMissingEpisode()
self.assertEqual(len(missing), 1)
self.assertEqual(missing[0].name, "Anime 1")
class TestDatabaseManager(unittest.TestCase):
"""Test cases for DatabaseManager class."""
def setUp(self):
"""Set up test database."""
self.test_db = tempfile.NamedTemporaryFile(delete=False)
self.test_db.close()
self.db_manager = DatabaseManager(self.test_db.name)
def tearDown(self):
"""Clean up test database."""
self.db_manager.close()
os.unlink(self.test_db.name)
def test_database_initialization(self):
"""Test database initialization."""
# Check if tables exist
with self.db_manager.get_connection() as conn:
cursor = conn.execute("""
SELECT name FROM sqlite_master
WHERE type='table' AND name='anime_metadata'
""")
result = cursor.fetchone()
self.assertIsNotNone(result)
def test_schema_versioning(self):
"""Test schema version management."""
version = self.db_manager.get_current_version()
self.assertIsInstance(version, int)
self.assertGreater(version, 0)
def test_anime_crud_operations(self):
"""Test anime CRUD operations."""
# Create anime
anime = AnimeMetadata(
anime_id="test-123",
name="Test Anime",
folder="test_folder",
key="test-key"
)
# Insert
query = """
INSERT INTO anime_metadata
(anime_id, name, folder, key, created_at, last_updated)
VALUES (?, ?, ?, ?, ?, ?)
"""
params = (
anime.anime_id, anime.name, anime.folder, anime.key,
anime.created_at, anime.last_updated
)
success = self.db_manager.execute_update(query, params)
self.assertTrue(success)
# Read
select_query = "SELECT * FROM anime_metadata WHERE anime_id = ?"
results = self.db_manager.execute_query(select_query, (anime.anime_id,))
self.assertEqual(len(results), 1)
self.assertEqual(results[0]['name'], anime.name)
# Update
update_query = """
UPDATE anime_metadata SET description = ? WHERE anime_id = ?
"""
success = self.db_manager.execute_update(
update_query, ("Updated description", anime.anime_id)
)
self.assertTrue(success)
# Verify update
results = self.db_manager.execute_query(select_query, (anime.anime_id,))
self.assertEqual(results[0]['description'], "Updated description")
# Delete
delete_query = "DELETE FROM anime_metadata WHERE anime_id = ?"
success = self.db_manager.execute_update(delete_query, (anime.anime_id,))
self.assertTrue(success)
# Verify deletion
results = self.db_manager.execute_query(select_query, (anime.anime_id,))
self.assertEqual(len(results), 0)
class TestErrorRecoveryManager(unittest.TestCase):
"""Test cases for ErrorRecoveryManager."""
def setUp(self):
"""Set up error recovery manager."""
self.recovery_manager = ErrorRecoveryManager()
def test_retry_mechanism(self):
"""Test retry mechanism for failed operations."""
retry_mechanism = RetryMechanism(max_retries=3, base_delay=0.1)
# Test successful operation
def success_operation():
return "success"
result = retry_mechanism.execute_with_retry(success_operation)
self.assertEqual(result, "success")
# Test failing operation
call_count = [0]
def failing_operation():
call_count[0] += 1
if call_count[0] < 3:
raise Exception("Temporary failure")
return "success"
result = retry_mechanism.execute_with_retry(failing_operation)
self.assertEqual(result, "success")
self.assertEqual(call_count[0], 3)
def test_network_health_checker(self):
"""Test network health checking."""
checker = NetworkHealthChecker()
# Mock requests for controlled testing
with patch('requests.get') as mock_get:
# Test successful check
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status.return_value = None
mock_get.return_value = mock_response
is_healthy = checker.check_network_health()
self.assertTrue(is_healthy)
# Test failed check
mock_get.side_effect = Exception("Network error")
is_healthy = checker.check_network_health()
self.assertFalse(is_healthy)
class TestPerformanceOptimizer(unittest.TestCase):
"""Test cases for performance optimization components."""
def setUp(self):
"""Set up performance components."""
self.speed_limiter = SpeedLimiter(max_speed_mbps=10)
self.download_cache = DownloadCache()
def test_speed_limiter(self):
"""Test download speed limiting."""
# Test speed calculation
speed_mbps = self.speed_limiter.calculate_current_speed(1024*1024, 1.0) # 1MB in 1 second
self.assertEqual(speed_mbps, 8.0) # 1MB/s = 8 Mbps
# Test should limit
should_limit = self.speed_limiter.should_limit_speed(15.0) # Above limit
self.assertTrue(should_limit)
should_not_limit = self.speed_limiter.should_limit_speed(5.0) # Below limit
self.assertFalse(should_not_limit)
def test_download_cache(self):
"""Test download caching mechanism."""
test_url = "http://example.com/video.mp4"
test_data = b"test video data"
# Test cache miss
cached_data = self.download_cache.get(test_url)
self.assertIsNone(cached_data)
# Test cache set and hit
self.download_cache.set(test_url, test_data)
cached_data = self.download_cache.get(test_url)
self.assertEqual(cached_data, test_data)
# Test cache invalidation
self.download_cache.invalidate(test_url)
cached_data = self.download_cache.get(test_url)
self.assertIsNone(cached_data)
def test_memory_monitor(self):
"""Test memory monitoring."""
monitor = MemoryMonitor(threshold_mb=100)
# Test memory usage calculation
usage_mb = monitor.get_current_memory_usage()
self.assertIsInstance(usage_mb, (int, float))
self.assertGreater(usage_mb, 0)
# Test threshold checking
is_high = monitor.is_memory_usage_high()
self.assertIsInstance(is_high, bool)
class TestAPIIntegration(unittest.TestCase):
"""Test cases for API integration components."""
def setUp(self):
"""Set up API components."""
self.webhook_manager = WebhookManager()
self.export_manager = ExportManager()
def test_webhook_manager(self):
"""Test webhook functionality."""
test_url = "https://example.com/webhook"
self.webhook_manager.add_webhook(test_url)
# Test webhook is registered
self.assertIn(test_url, self.webhook_manager.webhooks)
# Test webhook removal
self.webhook_manager.remove_webhook(test_url)
self.assertNotIn(test_url, self.webhook_manager.webhooks)
def test_export_manager(self):
"""Test data export functionality."""
# Mock series app
mock_series_app = Mock()
mock_series = Mock()
mock_series.name = "Test Anime"
mock_series.folder = "test_folder"
mock_series.missing = [1, 2, 3]
mock_series_app.series_list.series = [mock_series]
self.export_manager.series_app = mock_series_app
# Test JSON export
json_data = self.export_manager.export_to_json()
self.assertIsInstance(json_data, str)
# Parse and validate JSON
parsed_data = json.loads(json_data)
self.assertIn('anime_list', parsed_data)
self.assertEqual(len(parsed_data['anime_list']), 1)
self.assertEqual(parsed_data['anime_list'][0]['name'], "Test Anime")
# Test CSV export
csv_data = self.export_manager.export_to_csv()
self.assertIsInstance(csv_data, str)
self.assertIn("Test Anime", csv_data)
self.assertIn("test_folder", csv_data)
class TestBackupManager(unittest.TestCase):
"""Test cases for backup management."""
def setUp(self):
"""Set up test environment."""
self.temp_dir = tempfile.mkdtemp()
# Create test database
self.test_db = os.path.join(self.temp_dir, "test.db")
self.db_manager = DatabaseManager(self.test_db)
# Create backup manager
self.backup_manager = BackupManager(
self.db_manager,
os.path.join(self.temp_dir, "backups")
)
def tearDown(self):
"""Clean up test environment."""
self.db_manager.close()
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_create_backup(self):
"""Test backup creation."""
# Add some test data
anime = AnimeMetadata(
anime_id="backup-test",
name="Backup Test Anime",
folder="backup_test"
)
with self.db_manager.get_connection() as conn:
conn.execute("""
INSERT INTO anime_metadata
(anime_id, name, folder, created_at, last_updated)
VALUES (?, ?, ?, ?, ?)
""", (anime.anime_id, anime.name, anime.folder,
anime.created_at, anime.last_updated))
# Create backup
backup_info = self.backup_manager.create_full_backup("Test backup")
self.assertIsNotNone(backup_info)
self.assertTrue(os.path.exists(backup_info.backup_path))
self.assertGreater(backup_info.size_bytes, 0)
def test_restore_backup(self):
"""Test backup restoration."""
# Create initial data
anime_id = "restore-test"
with self.db_manager.get_connection() as conn:
conn.execute("""
INSERT INTO anime_metadata
(anime_id, name, folder, created_at, last_updated)
VALUES (?, ?, ?, ?, ?)
""", (anime_id, "Original", "original_folder",
datetime.utcnow(), datetime.utcnow()))
# Create backup
backup_info = self.backup_manager.create_full_backup("Pre-modification backup")
# Modify data
with self.db_manager.get_connection() as conn:
conn.execute("""
UPDATE anime_metadata SET name = ? WHERE anime_id = ?
""", ("Modified", anime_id))
# Restore backup
success = self.backup_manager.restore_backup(backup_info.backup_id)
self.assertTrue(success)
# Verify restoration
results = self.db_manager.execute_query(
"SELECT name FROM anime_metadata WHERE anime_id = ?",
(anime_id,)
)
self.assertEqual(len(results), 1)
self.assertEqual(results[0]['name'], "Original")
class TestConcurrency(unittest.TestCase):
"""Test cases for concurrent operations."""
def test_concurrent_downloads(self):
"""Test concurrent download handling."""
results = []
errors = []
def mock_download(episode_id):
"""Mock download function."""
try:
# Simulate download work
threading.Event().wait(0.1)
results.append(f"Downloaded {episode_id}")
return True
except Exception as e:
errors.append(str(e))
return False
# Create multiple download threads
threads = []
for i in range(5):
thread = threading.Thread(target=mock_download, args=(f"episode_{i}",))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
# Verify results
self.assertEqual(len(results), 5)
self.assertEqual(len(errors), 0)
def test_database_concurrent_access(self):
"""Test concurrent database access."""
# Create temporary database
temp_db = tempfile.NamedTemporaryFile(delete=False)
temp_db.close()
try:
db_manager = DatabaseManager(temp_db.name)
results = []
errors = []
def concurrent_insert(thread_id):
"""Concurrent database insert operation."""
try:
anime_id = f"concurrent-{thread_id}"
query = """
INSERT INTO anime_metadata
(anime_id, name, folder, created_at, last_updated)
VALUES (?, ?, ?, ?, ?)
"""
success = db_manager.execute_update(
query,
(anime_id, f"Anime {thread_id}", f"folder_{thread_id}",
datetime.utcnow(), datetime.utcnow())
)
if success:
results.append(thread_id)
except Exception as e:
errors.append(str(e))
# Create concurrent threads
threads = []
for i in range(10):
thread = threading.Thread(target=concurrent_insert, args=(i,))
threads.append(thread)
thread.start()
# Wait for completion
for thread in threads:
thread.join()
# Verify results
self.assertEqual(len(results), 10)
self.assertEqual(len(errors), 0)
# Verify database state
count_results = db_manager.execute_query(
"SELECT COUNT(*) as count FROM anime_metadata"
)
self.assertEqual(count_results[0]['count'], 10)
db_manager.close()
finally:
os.unlink(temp_db.name)
def run_test_suite():
"""Run the complete test suite."""
# Create test suite
suite = unittest.TestSuite()
# Add all test cases
test_classes = [
TestSerie,
TestSeriesList,
TestDatabaseManager,
TestErrorRecoveryManager,
TestPerformanceOptimizer,
TestAPIIntegration,
TestBackupManager,
TestConcurrency
]
for test_class in test_classes:
tests = unittest.TestLoader().loadTestsFromTestCase(test_class)
suite.addTests(tests)
# Run tests
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
return result
if __name__ == '__main__':
print("Running AniWorld Unit Tests...")
print("=" * 50)
result = run_test_suite()
print("\n" + "=" * 50)
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}")
if result.wasSuccessful():
print("\nAll tests passed! ✅")
sys.exit(0)
else:
print("\nSome tests failed! ❌")
sys.exit(1)