new folder structure
This commit is contained in:
593
tests/unit/core/test_core.py
Normal file
593
tests/unit/core/test_core.py
Normal 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)
|
||||
Reference in New Issue
Block a user