""" Test cases for anime API endpoints. """ import pytest from unittest.mock import Mock, patch, MagicMock from flask import Flask import json # Mock the database managers first mock_anime_manager = Mock() mock_download_manager = Mock() # Import the modules to test try: with patch.dict('sys.modules', { 'src.server.data.anime_manager': Mock(AnimeManager=Mock(return_value=mock_anime_manager)), 'src.server.data.download_manager': Mock(DownloadManager=Mock(return_value=mock_download_manager)) }): from src.server.web.controllers.api.v1.anime import anime_bp except ImportError: anime_bp = None class TestAnimeEndpoints: """Test cases for anime API endpoints.""" @pytest.fixture def app(self): """Create a test Flask application.""" if not anime_bp: pytest.skip("Module not available") app = Flask(__name__) app.config['TESTING'] = True app.register_blueprint(anime_bp, url_prefix='/api/v1') return app @pytest.fixture def client(self, app): """Create a test client.""" return app.test_client() @pytest.fixture def mock_session(self): """Mock session for authentication.""" with patch('src.server.web.controllers.shared.auth_decorators.session') as mock_session: mock_session.get.return_value = {'user_id': 1, 'username': 'testuser'} yield mock_session def setup_method(self): """Reset mocks before each test.""" mock_anime_manager.reset_mock() mock_download_manager.reset_mock() def test_list_anime_success(self, client, mock_session): """Test GET /anime - list anime with pagination.""" if not anime_bp: pytest.skip("Module not available") # Mock anime data mock_anime_list = [ {'id': 1, 'name': 'Anime 1', 'url': 'https://example.com/1'}, {'id': 2, 'name': 'Anime 2', 'url': 'https://example.com/2'} ] mock_anime_manager.get_all_anime.return_value = mock_anime_list mock_anime_manager.get_anime_count.return_value = 2 response = client.get('/api/v1/anime?page=1&per_page=10') assert response.status_code == 200 data = json.loads(response.data) assert 'data' in data assert 'pagination' in data assert len(data['data']) == 2 # Verify manager was called with correct parameters mock_anime_manager.get_all_anime.assert_called_once_with( offset=0, limit=10, search=None, status=None, sort_by='name', sort_order='asc' ) def test_list_anime_with_search(self, client, mock_session): """Test GET /anime with search parameter.""" if not anime_bp: pytest.skip("Module not available") mock_anime_manager.get_all_anime.return_value = [] mock_anime_manager.get_anime_count.return_value = 0 response = client.get('/api/v1/anime?search=naruto&status=completed') assert response.status_code == 200 mock_anime_manager.get_all_anime.assert_called_once_with( offset=0, limit=20, search='naruto', status='completed', sort_by='name', sort_order='asc' ) def test_get_anime_by_id_success(self, client, mock_session): """Test GET /anime/ - get specific anime.""" if not anime_bp: pytest.skip("Module not available") mock_anime = { 'id': 1, 'name': 'Test Anime', 'url': 'https://example.com/1', 'description': 'A test anime' } mock_anime_manager.get_anime_by_id.return_value = mock_anime response = client.get('/api/v1/anime/1') assert response.status_code == 200 data = json.loads(response.data) assert data['data']['id'] == 1 assert data['data']['name'] == 'Test Anime' mock_anime_manager.get_anime_by_id.assert_called_once_with(1) def test_get_anime_by_id_not_found(self, client, mock_session): """Test GET /anime/ - anime not found.""" if not anime_bp: pytest.skip("Module not available") mock_anime_manager.get_anime_by_id.return_value = None response = client.get('/api/v1/anime/999') assert response.status_code == 404 data = json.loads(response.data) assert 'error' in data assert 'not found' in data['error'].lower() def test_create_anime_success(self, client, mock_session): """Test POST /anime - create new anime.""" if not anime_bp: pytest.skip("Module not available") anime_data = { 'name': 'New Anime', 'url': 'https://example.com/new-anime', 'description': 'A new anime', 'episodes': 12, 'status': 'ongoing' } mock_anime_manager.create_anime.return_value = 1 mock_anime_manager.get_anime_by_id.return_value = { 'id': 1, **anime_data } response = client.post('/api/v1/anime', json=anime_data, content_type='application/json') assert response.status_code == 201 data = json.loads(response.data) assert data['success'] is True assert data['data']['id'] == 1 assert data['data']['name'] == 'New Anime' mock_anime_manager.create_anime.assert_called_once() def test_create_anime_validation_error(self, client, mock_session): """Test POST /anime - validation error.""" if not anime_bp: pytest.skip("Module not available") # Missing required fields anime_data = { 'description': 'A new anime' } response = client.post('/api/v1/anime', json=anime_data, content_type='application/json') assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data def test_create_anime_duplicate(self, client, mock_session): """Test POST /anime - duplicate anime.""" if not anime_bp: pytest.skip("Module not available") anime_data = { 'name': 'Existing Anime', 'url': 'https://example.com/existing' } # Simulate duplicate error mock_anime_manager.create_anime.side_effect = Exception("Duplicate entry") response = client.post('/api/v1/anime', json=anime_data, content_type='application/json') assert response.status_code == 500 data = json.loads(response.data) assert 'error' in data def test_update_anime_success(self, client, mock_session): """Test PUT /anime/ - update anime.""" if not anime_bp: pytest.skip("Module not available") update_data = { 'name': 'Updated Anime', 'description': 'Updated description', 'status': 'completed' } mock_anime_manager.get_anime_by_id.return_value = { 'id': 1, 'name': 'Original Anime' } mock_anime_manager.update_anime.return_value = True response = client.put('/api/v1/anime/1', json=update_data, content_type='application/json') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True mock_anime_manager.update_anime.assert_called_once_with(1, update_data) def test_update_anime_not_found(self, client, mock_session): """Test PUT /anime/ - anime not found.""" if not anime_bp: pytest.skip("Module not available") mock_anime_manager.get_anime_by_id.return_value = None response = client.put('/api/v1/anime/999', json={'name': 'Updated'}, content_type='application/json') assert response.status_code == 404 data = json.loads(response.data) assert 'error' in data def test_delete_anime_success(self, client, mock_session): """Test DELETE /anime/ - delete anime.""" if not anime_bp: pytest.skip("Module not available") mock_anime_manager.get_anime_by_id.return_value = { 'id': 1, 'name': 'Test Anime' } mock_anime_manager.delete_anime.return_value = True response = client.delete('/api/v1/anime/1') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True mock_anime_manager.delete_anime.assert_called_once_with(1) def test_delete_anime_not_found(self, client, mock_session): """Test DELETE /anime/ - anime not found.""" if not anime_bp: pytest.skip("Module not available") mock_anime_manager.get_anime_by_id.return_value = None response = client.delete('/api/v1/anime/999') assert response.status_code == 404 data = json.loads(response.data) assert 'error' in data def test_bulk_create_anime_success(self, client, mock_session): """Test POST /anime/bulk - bulk create anime.""" if not anime_bp: pytest.skip("Module not available") bulk_data = { 'anime_list': [ {'name': 'Anime 1', 'url': 'https://example.com/1'}, {'name': 'Anime 2', 'url': 'https://example.com/2'} ] } mock_anime_manager.bulk_create_anime.return_value = { 'created': 2, 'failed': 0, 'created_ids': [1, 2] } response = client.post('/api/v1/anime/bulk', json=bulk_data, content_type='application/json') assert response.status_code == 201 data = json.loads(response.data) assert data['success'] is True assert data['data']['created'] == 2 assert data['data']['failed'] == 0 def test_bulk_update_anime_success(self, client, mock_session): """Test PUT /anime/bulk - bulk update anime.""" if not anime_bp: pytest.skip("Module not available") bulk_data = { 'updates': [ {'id': 1, 'status': 'completed'}, {'id': 2, 'status': 'completed'} ] } mock_anime_manager.bulk_update_anime.return_value = { 'updated': 2, 'failed': 0 } response = client.put('/api/v1/anime/bulk', json=bulk_data, content_type='application/json') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True assert data['data']['updated'] == 2 def test_bulk_delete_anime_success(self, client, mock_session): """Test DELETE /anime/bulk - bulk delete anime.""" if not anime_bp: pytest.skip("Module not available") bulk_data = { 'anime_ids': [1, 2, 3] } mock_anime_manager.bulk_delete_anime.return_value = { 'deleted': 3, 'failed': 0 } response = client.delete('/api/v1/anime/bulk', json=bulk_data, content_type='application/json') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True assert data['data']['deleted'] == 3 def test_get_anime_episodes_success(self, client, mock_session): """Test GET /anime//episodes - get anime episodes.""" if not anime_bp: pytest.skip("Module not available") mock_anime_manager.get_anime_by_id.return_value = { 'id': 1, 'name': 'Test Anime' } mock_episodes = [ {'id': 1, 'number': 1, 'title': 'Episode 1'}, {'id': 2, 'number': 2, 'title': 'Episode 2'} ] mock_anime_manager.get_anime_episodes.return_value = mock_episodes response = client.get('/api/v1/anime/1/episodes') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True assert len(data['data']) == 2 assert data['data'][0]['number'] == 1 def test_get_anime_stats_success(self, client, mock_session): """Test GET /anime//stats - get anime statistics.""" if not anime_bp: pytest.skip("Module not available") mock_anime_manager.get_anime_by_id.return_value = { 'id': 1, 'name': 'Test Anime' } mock_stats = { 'total_episodes': 12, 'downloaded_episodes': 8, 'download_progress': 66.7, 'total_size': 1073741824, # 1GB 'downloaded_size': 715827882 # ~680MB } mock_anime_manager.get_anime_stats.return_value = mock_stats response = client.get('/api/v1/anime/1/stats') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True assert data['data']['total_episodes'] == 12 assert data['data']['downloaded_episodes'] == 8 def test_search_anime_success(self, client, mock_session): """Test GET /anime/search - search anime.""" if not anime_bp: pytest.skip("Module not available") mock_results = [ {'id': 1, 'name': 'Naruto', 'url': 'https://example.com/naruto'}, {'id': 2, 'name': 'Naruto Shippuden', 'url': 'https://example.com/naruto-shippuden'} ] mock_anime_manager.search_anime.return_value = mock_results response = client.get('/api/v1/anime/search?q=naruto') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True assert len(data['data']) == 2 assert 'naruto' in data['data'][0]['name'].lower() mock_anime_manager.search_anime.assert_called_once_with('naruto', limit=20) def test_search_anime_no_query(self, client, mock_session): """Test GET /anime/search - missing search query.""" if not anime_bp: pytest.skip("Module not available") response = client.get('/api/v1/anime/search') assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data assert 'query parameter' in data['error'].lower() class TestAnimeAuthentication: """Test cases for anime endpoints authentication.""" @pytest.fixture def app(self): """Create a test Flask application.""" if not anime_bp: pytest.skip("Module not available") app = Flask(__name__) app.config['TESTING'] = True app.register_blueprint(anime_bp, url_prefix='/api/v1') return app @pytest.fixture def client(self, app): """Create a test client.""" return app.test_client() def test_unauthenticated_read_access(self, client): """Test that read operations work without authentication.""" if not anime_bp: pytest.skip("Module not available") with patch('src.server.web.controllers.shared.auth_decorators.session') as mock_session: mock_session.get.return_value = None # No authentication mock_anime_manager.get_all_anime.return_value = [] mock_anime_manager.get_anime_count.return_value = 0 response = client.get('/api/v1/anime') # Should still work for read operations assert response.status_code == 200 def test_authenticated_write_access(self, client): """Test that write operations require authentication.""" if not anime_bp: pytest.skip("Module not available") with patch('src.server.web.controllers.shared.auth_decorators.session') as mock_session: mock_session.get.return_value = None # No authentication response = client.post('/api/v1/anime', json={'name': 'Test'}, content_type='application/json') # Should require authentication for write operations assert response.status_code == 401 class TestAnimeErrorHandling: """Test cases for anime endpoints error handling.""" @pytest.fixture def app(self): """Create a test Flask application.""" if not anime_bp: pytest.skip("Module not available") app = Flask(__name__) app.config['TESTING'] = True app.register_blueprint(anime_bp, url_prefix='/api/v1') return app @pytest.fixture def client(self, app): """Create a test client.""" return app.test_client() @pytest.fixture def mock_session(self): """Mock session for authentication.""" with patch('src.server.web.controllers.shared.auth_decorators.session') as mock_session: mock_session.get.return_value = {'user_id': 1, 'username': 'testuser'} yield mock_session def test_database_error_handling(self, client, mock_session): """Test handling of database errors.""" if not anime_bp: pytest.skip("Module not available") # Simulate database error mock_anime_manager.get_all_anime.side_effect = Exception("Database connection failed") response = client.get('/api/v1/anime') assert response.status_code == 500 data = json.loads(response.data) assert 'error' in data def test_invalid_json_handling(self, client, mock_session): """Test handling of invalid JSON.""" if not anime_bp: pytest.skip("Module not available") response = client.post('/api/v1/anime', data='invalid json', content_type='application/json') assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data def test_method_not_allowed(self, client): """Test method not allowed responses.""" if not anime_bp: pytest.skip("Module not available") response = client.patch('/api/v1/anime/1') assert response.status_code == 405 if __name__ == '__main__': pytest.main([__file__])