cleanup contollers
This commit is contained in:
557
tests/unit/web/controllers/api/v1/test_anime.py
Normal file
557
tests/unit/web/controllers/api/v1/test_anime.py
Normal file
@@ -0,0 +1,557 @@
|
||||
"""
|
||||
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/<id> - 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/<id> - 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/<id> - 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/<id> - 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/<id> - 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/<id> - 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/<id>/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/<id>/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__])
|
||||
717
tests/unit/web/controllers/api/v1/test_downloads.py
Normal file
717
tests/unit/web/controllers/api/v1/test_downloads.py
Normal file
@@ -0,0 +1,717 @@
|
||||
"""
|
||||
Test cases for downloads API endpoints.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from flask import Flask
|
||||
import json
|
||||
|
||||
# Mock the database managers first
|
||||
mock_download_manager = Mock()
|
||||
mock_episode_manager = Mock()
|
||||
mock_anime_manager = Mock()
|
||||
|
||||
# Import the modules to test
|
||||
try:
|
||||
with patch.dict('sys.modules', {
|
||||
'src.server.data.download_manager': Mock(DownloadManager=Mock(return_value=mock_download_manager)),
|
||||
'src.server.data.episode_manager': Mock(EpisodeManager=Mock(return_value=mock_episode_manager)),
|
||||
'src.server.data.anime_manager': Mock(AnimeManager=Mock(return_value=mock_anime_manager))
|
||||
}):
|
||||
from src.server.web.controllers.api.v1.downloads import downloads_bp
|
||||
except ImportError:
|
||||
downloads_bp = None
|
||||
|
||||
|
||||
class TestDownloadEndpoints:
|
||||
"""Test cases for download API endpoints."""
|
||||
|
||||
@pytest.fixture
|
||||
def app(self):
|
||||
"""Create a test Flask application."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['TESTING'] = True
|
||||
app.register_blueprint(downloads_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_download_manager.reset_mock()
|
||||
mock_episode_manager.reset_mock()
|
||||
mock_anime_manager.reset_mock()
|
||||
|
||||
def test_list_downloads_success(self, client, mock_session):
|
||||
"""Test GET /downloads - list downloads with pagination."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_downloads = [
|
||||
{
|
||||
'id': 1,
|
||||
'anime_id': 1,
|
||||
'episode_id': 1,
|
||||
'status': 'downloading',
|
||||
'progress': 45.5,
|
||||
'size': 1073741824, # 1GB
|
||||
'downloaded_size': 488447385, # ~465MB
|
||||
'speed': 1048576, # 1MB/s
|
||||
'eta': 600, # 10 minutes
|
||||
'created_at': '2023-01-01 12:00:00'
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'anime_id': 1,
|
||||
'episode_id': 2,
|
||||
'status': 'completed',
|
||||
'progress': 100.0,
|
||||
'size': 1073741824,
|
||||
'downloaded_size': 1073741824,
|
||||
'created_at': '2023-01-01 11:00:00',
|
||||
'completed_at': '2023-01-01 11:30:00'
|
||||
}
|
||||
]
|
||||
|
||||
mock_download_manager.get_all_downloads.return_value = mock_downloads
|
||||
mock_download_manager.get_downloads_count.return_value = 2
|
||||
|
||||
response = client.get('/api/v1/downloads?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
|
||||
assert data['data'][0]['status'] == 'downloading'
|
||||
|
||||
mock_download_manager.get_all_downloads.assert_called_once_with(
|
||||
offset=0, limit=10, status=None, anime_id=None, sort_by='created_at', sort_order='desc'
|
||||
)
|
||||
|
||||
def test_list_downloads_with_filters(self, client, mock_session):
|
||||
"""Test GET /downloads with filters."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_all_downloads.return_value = []
|
||||
mock_download_manager.get_downloads_count.return_value = 0
|
||||
|
||||
response = client.get('/api/v1/downloads?status=completed&anime_id=5&sort_by=progress')
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_download_manager.get_all_downloads.assert_called_once_with(
|
||||
offset=0, limit=20, status='completed', anime_id=5, sort_by='progress', sort_order='desc'
|
||||
)
|
||||
|
||||
def test_get_download_by_id_success(self, client, mock_session):
|
||||
"""Test GET /downloads/<id> - get specific download."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download = {
|
||||
'id': 1,
|
||||
'anime_id': 1,
|
||||
'episode_id': 1,
|
||||
'status': 'downloading',
|
||||
'progress': 75.0,
|
||||
'size': 1073741824,
|
||||
'downloaded_size': 805306368,
|
||||
'speed': 2097152, # 2MB/s
|
||||
'eta': 150, # 2.5 minutes
|
||||
'file_path': '/downloads/anime1/episode1.mp4',
|
||||
'created_at': '2023-01-01 12:00:00',
|
||||
'started_at': '2023-01-01 12:05:00'
|
||||
}
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = mock_download
|
||||
|
||||
response = client.get('/api/v1/downloads/1')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert data['data']['id'] == 1
|
||||
assert data['data']['progress'] == 75.0
|
||||
assert data['data']['status'] == 'downloading'
|
||||
|
||||
mock_download_manager.get_download_by_id.assert_called_once_with(1)
|
||||
|
||||
def test_get_download_by_id_not_found(self, client, mock_session):
|
||||
"""Test GET /downloads/<id> - download not found."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = None
|
||||
|
||||
response = client.get('/api/v1/downloads/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_download_success(self, client, mock_session):
|
||||
"""Test POST /downloads - create new download."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
download_data = {
|
||||
'episode_id': 1,
|
||||
'quality': '1080p',
|
||||
'priority': 'normal'
|
||||
}
|
||||
|
||||
# Mock episode exists
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
'anime_id': 1,
|
||||
'title': 'Episode 1',
|
||||
'url': 'https://example.com/episode/1'
|
||||
}
|
||||
|
||||
mock_download_manager.create_download.return_value = 1
|
||||
mock_download_manager.get_download_by_id.return_value = {
|
||||
'id': 1,
|
||||
'episode_id': 1,
|
||||
'status': 'queued',
|
||||
'progress': 0.0
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/downloads',
|
||||
json=download_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']['status'] == 'queued'
|
||||
|
||||
mock_download_manager.create_download.assert_called_once()
|
||||
|
||||
def test_create_download_invalid_episode(self, client, mock_session):
|
||||
"""Test POST /downloads - invalid episode_id."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
download_data = {
|
||||
'episode_id': 999,
|
||||
'quality': '1080p'
|
||||
}
|
||||
|
||||
# Mock episode doesn't exist
|
||||
mock_episode_manager.get_episode_by_id.return_value = None
|
||||
|
||||
response = client.post('/api/v1/downloads',
|
||||
json=download_data,
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 400
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
assert 'episode' in data['error'].lower()
|
||||
|
||||
def test_create_download_validation_error(self, client, mock_session):
|
||||
"""Test POST /downloads - validation error."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
# Missing required fields
|
||||
download_data = {
|
||||
'quality': '1080p'
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/downloads',
|
||||
json=download_data,
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 400
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
def test_pause_download_success(self, client, mock_session):
|
||||
"""Test PUT /downloads/<id>/pause - pause download."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = {
|
||||
'id': 1,
|
||||
'status': 'downloading'
|
||||
}
|
||||
mock_download_manager.pause_download.return_value = True
|
||||
|
||||
response = client.put('/api/v1/downloads/1/pause')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert 'paused' in data['message'].lower()
|
||||
|
||||
mock_download_manager.pause_download.assert_called_once_with(1)
|
||||
|
||||
def test_pause_download_not_found(self, client, mock_session):
|
||||
"""Test PUT /downloads/<id>/pause - download not found."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = None
|
||||
|
||||
response = client.put('/api/v1/downloads/999/pause')
|
||||
|
||||
assert response.status_code == 404
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
def test_resume_download_success(self, client, mock_session):
|
||||
"""Test PUT /downloads/<id>/resume - resume download."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = {
|
||||
'id': 1,
|
||||
'status': 'paused'
|
||||
}
|
||||
mock_download_manager.resume_download.return_value = True
|
||||
|
||||
response = client.put('/api/v1/downloads/1/resume')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert 'resumed' in data['message'].lower()
|
||||
|
||||
mock_download_manager.resume_download.assert_called_once_with(1)
|
||||
|
||||
def test_cancel_download_success(self, client, mock_session):
|
||||
"""Test DELETE /downloads/<id> - cancel download."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = {
|
||||
'id': 1,
|
||||
'status': 'downloading'
|
||||
}
|
||||
mock_download_manager.cancel_download.return_value = True
|
||||
|
||||
response = client.delete('/api/v1/downloads/1')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert 'cancelled' in data['message'].lower()
|
||||
|
||||
mock_download_manager.cancel_download.assert_called_once_with(1)
|
||||
|
||||
def test_get_download_queue_success(self, client, mock_session):
|
||||
"""Test GET /downloads/queue - get download queue."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_queue = [
|
||||
{
|
||||
'id': 1,
|
||||
'episode_id': 1,
|
||||
'status': 'downloading',
|
||||
'progress': 25.0,
|
||||
'position': 1
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'episode_id': 2,
|
||||
'status': 'queued',
|
||||
'progress': 0.0,
|
||||
'position': 2
|
||||
}
|
||||
]
|
||||
|
||||
mock_download_manager.get_download_queue.return_value = mock_queue
|
||||
|
||||
response = client.get('/api/v1/downloads/queue')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert len(data['data']) == 2
|
||||
assert data['data'][0]['status'] == 'downloading'
|
||||
assert data['data'][1]['status'] == 'queued'
|
||||
|
||||
def test_reorder_download_queue_success(self, client, mock_session):
|
||||
"""Test PUT /downloads/queue/reorder - reorder download queue."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
reorder_data = {
|
||||
'download_ids': [3, 1, 2] # New order
|
||||
}
|
||||
|
||||
mock_download_manager.reorder_download_queue.return_value = True
|
||||
|
||||
response = client.put('/api/v1/downloads/queue/reorder',
|
||||
json=reorder_data,
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
|
||||
mock_download_manager.reorder_download_queue.assert_called_once_with([3, 1, 2])
|
||||
|
||||
def test_clear_download_queue_success(self, client, mock_session):
|
||||
"""Test DELETE /downloads/queue - clear download queue."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.clear_download_queue.return_value = {
|
||||
'cleared': 5,
|
||||
'failed': 0
|
||||
}
|
||||
|
||||
response = client.delete('/api/v1/downloads/queue')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert data['data']['cleared'] == 5
|
||||
|
||||
mock_download_manager.clear_download_queue.assert_called_once()
|
||||
|
||||
def test_get_download_history_success(self, client, mock_session):
|
||||
"""Test GET /downloads/history - get download history."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_history = [
|
||||
{
|
||||
'id': 1,
|
||||
'episode_id': 1,
|
||||
'status': 'completed',
|
||||
'completed_at': '2023-01-01 12:30:00',
|
||||
'file_size': 1073741824
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'episode_id': 2,
|
||||
'status': 'failed',
|
||||
'failed_at': '2023-01-01 11:45:00',
|
||||
'error_message': 'Network timeout'
|
||||
}
|
||||
]
|
||||
|
||||
mock_download_manager.get_download_history.return_value = mock_history
|
||||
mock_download_manager.get_history_count.return_value = 2
|
||||
|
||||
response = client.get('/api/v1/downloads/history?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
|
||||
assert data['data'][0]['status'] == 'completed'
|
||||
|
||||
def test_bulk_create_downloads_success(self, client, mock_session):
|
||||
"""Test POST /downloads/bulk - bulk create downloads."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
bulk_data = {
|
||||
'downloads': [
|
||||
{'episode_id': 1, 'quality': '1080p'},
|
||||
{'episode_id': 2, 'quality': '720p'},
|
||||
{'episode_id': 3, 'quality': '1080p'}
|
||||
]
|
||||
}
|
||||
|
||||
mock_download_manager.bulk_create_downloads.return_value = {
|
||||
'created': 3,
|
||||
'failed': 0,
|
||||
'created_ids': [1, 2, 3]
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/downloads/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'] == 3
|
||||
assert data['data']['failed'] == 0
|
||||
|
||||
def test_bulk_pause_downloads_success(self, client, mock_session):
|
||||
"""Test PUT /downloads/bulk/pause - bulk pause downloads."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
bulk_data = {
|
||||
'download_ids': [1, 2, 3]
|
||||
}
|
||||
|
||||
mock_download_manager.bulk_pause_downloads.return_value = {
|
||||
'paused': 3,
|
||||
'failed': 0
|
||||
}
|
||||
|
||||
response = client.put('/api/v1/downloads/bulk/pause',
|
||||
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']['paused'] == 3
|
||||
|
||||
def test_bulk_resume_downloads_success(self, client, mock_session):
|
||||
"""Test PUT /downloads/bulk/resume - bulk resume downloads."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
bulk_data = {
|
||||
'download_ids': [1, 2, 3]
|
||||
}
|
||||
|
||||
mock_download_manager.bulk_resume_downloads.return_value = {
|
||||
'resumed': 3,
|
||||
'failed': 0
|
||||
}
|
||||
|
||||
response = client.put('/api/v1/downloads/bulk/resume',
|
||||
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']['resumed'] == 3
|
||||
|
||||
def test_bulk_cancel_downloads_success(self, client, mock_session):
|
||||
"""Test DELETE /downloads/bulk - bulk cancel downloads."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
bulk_data = {
|
||||
'download_ids': [1, 2, 3]
|
||||
}
|
||||
|
||||
mock_download_manager.bulk_cancel_downloads.return_value = {
|
||||
'cancelled': 3,
|
||||
'failed': 0
|
||||
}
|
||||
|
||||
response = client.delete('/api/v1/downloads/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']['cancelled'] == 3
|
||||
|
||||
def test_get_download_stats_success(self, client, mock_session):
|
||||
"""Test GET /downloads/stats - get download statistics."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_stats = {
|
||||
'total_downloads': 150,
|
||||
'completed_downloads': 125,
|
||||
'active_downloads': 3,
|
||||
'failed_downloads': 22,
|
||||
'total_size_downloaded': 107374182400, # 100GB
|
||||
'average_speed': 2097152, # 2MB/s
|
||||
'queue_size': 5
|
||||
}
|
||||
|
||||
mock_download_manager.get_download_stats.return_value = mock_stats
|
||||
|
||||
response = client.get('/api/v1/downloads/stats')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert data['data']['total_downloads'] == 150
|
||||
assert data['data']['completed_downloads'] == 125
|
||||
assert data['data']['active_downloads'] == 3
|
||||
|
||||
def test_retry_failed_download_success(self, client, mock_session):
|
||||
"""Test PUT /downloads/<id>/retry - retry failed download."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = {
|
||||
'id': 1,
|
||||
'status': 'failed'
|
||||
}
|
||||
mock_download_manager.retry_download.return_value = True
|
||||
|
||||
response = client.put('/api/v1/downloads/1/retry')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert 'retrying' in data['message'].lower()
|
||||
|
||||
mock_download_manager.retry_download.assert_called_once_with(1)
|
||||
|
||||
def test_retry_download_invalid_status(self, client, mock_session):
|
||||
"""Test PUT /downloads/<id>/retry - retry download with invalid status."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = {
|
||||
'id': 1,
|
||||
'status': 'completed' # Can't retry completed downloads
|
||||
}
|
||||
|
||||
response = client.put('/api/v1/downloads/1/retry')
|
||||
|
||||
assert response.status_code == 400
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
assert 'cannot be retried' in data['error'].lower()
|
||||
|
||||
|
||||
class TestDownloadAuthentication:
|
||||
"""Test cases for download endpoints authentication."""
|
||||
|
||||
@pytest.fixture
|
||||
def app(self):
|
||||
"""Create a test Flask application."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['TESTING'] = True
|
||||
app.register_blueprint(downloads_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 downloads_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_download_manager.get_all_downloads.return_value = []
|
||||
mock_download_manager.get_downloads_count.return_value = 0
|
||||
|
||||
response = client.get('/api/v1/downloads')
|
||||
# Should work for read operations
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_authenticated_write_access(self, client):
|
||||
"""Test that write operations require authentication."""
|
||||
if not downloads_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/downloads',
|
||||
json={'episode_id': 1},
|
||||
content_type='application/json')
|
||||
# Should require authentication for write operations
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
class TestDownloadErrorHandling:
|
||||
"""Test cases for download endpoints error handling."""
|
||||
|
||||
@pytest.fixture
|
||||
def app(self):
|
||||
"""Create a test Flask application."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['TESTING'] = True
|
||||
app.register_blueprint(downloads_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 downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
# Simulate database error
|
||||
mock_download_manager.get_all_downloads.side_effect = Exception("Database connection failed")
|
||||
|
||||
response = client.get('/api/v1/downloads')
|
||||
|
||||
assert response.status_code == 500
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
def test_download_system_error(self, client, mock_session):
|
||||
"""Test handling of download system errors."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = {
|
||||
'id': 1,
|
||||
'status': 'downloading'
|
||||
}
|
||||
|
||||
# Simulate download system error
|
||||
mock_download_manager.pause_download.side_effect = Exception("Download system unavailable")
|
||||
|
||||
response = client.put('/api/v1/downloads/1/pause')
|
||||
|
||||
assert response.status_code == 500
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
def test_invalid_download_status_transition(self, client, mock_session):
|
||||
"""Test handling of invalid status transitions."""
|
||||
if not downloads_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_download_manager.get_download_by_id.return_value = {
|
||||
'id': 1,
|
||||
'status': 'completed'
|
||||
}
|
||||
|
||||
# Try to pause a completed download
|
||||
response = client.put('/api/v1/downloads/1/pause')
|
||||
|
||||
assert response.status_code == 400
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
assert 'cannot be paused' in data['error'].lower()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__])
|
||||
679
tests/unit/web/controllers/api/v1/test_episodes.py
Normal file
679
tests/unit/web/controllers/api/v1/test_episodes.py
Normal file
@@ -0,0 +1,679 @@
|
||||
"""
|
||||
Test cases for episodes API endpoints.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from flask import Flask
|
||||
import json
|
||||
|
||||
# Mock the database managers first
|
||||
mock_episode_manager = Mock()
|
||||
mock_anime_manager = Mock()
|
||||
mock_download_manager = Mock()
|
||||
|
||||
# Import the modules to test
|
||||
try:
|
||||
with patch.dict('sys.modules', {
|
||||
'src.server.data.episode_manager': Mock(EpisodeManager=Mock(return_value=mock_episode_manager)),
|
||||
'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.episodes import episodes_bp
|
||||
except ImportError:
|
||||
episodes_bp = None
|
||||
|
||||
|
||||
class TestEpisodeEndpoints:
|
||||
"""Test cases for episode API endpoints."""
|
||||
|
||||
@pytest.fixture
|
||||
def app(self):
|
||||
"""Create a test Flask application."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['TESTING'] = True
|
||||
app.register_blueprint(episodes_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_episode_manager.reset_mock()
|
||||
mock_anime_manager.reset_mock()
|
||||
mock_download_manager.reset_mock()
|
||||
|
||||
def test_list_episodes_success(self, client, mock_session):
|
||||
"""Test GET /episodes - list episodes with pagination."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episodes = [
|
||||
{
|
||||
'id': 1,
|
||||
'anime_id': 1,
|
||||
'number': 1,
|
||||
'title': 'Episode 1',
|
||||
'url': 'https://example.com/episode/1',
|
||||
'status': 'available'
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'anime_id': 1,
|
||||
'number': 2,
|
||||
'title': 'Episode 2',
|
||||
'url': 'https://example.com/episode/2',
|
||||
'status': 'available'
|
||||
}
|
||||
]
|
||||
|
||||
mock_episode_manager.get_all_episodes.return_value = mock_episodes
|
||||
mock_episode_manager.get_episodes_count.return_value = 2
|
||||
|
||||
response = client.get('/api/v1/episodes?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
|
||||
assert data['data'][0]['number'] == 1
|
||||
|
||||
mock_episode_manager.get_all_episodes.assert_called_once_with(
|
||||
offset=0, limit=10, anime_id=None, status=None, sort_by='number', sort_order='asc'
|
||||
)
|
||||
|
||||
def test_list_episodes_with_filters(self, client, mock_session):
|
||||
"""Test GET /episodes with filters."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_all_episodes.return_value = []
|
||||
mock_episode_manager.get_episodes_count.return_value = 0
|
||||
|
||||
response = client.get('/api/v1/episodes?anime_id=1&status=downloaded&sort_by=title&sort_order=desc')
|
||||
|
||||
assert response.status_code == 200
|
||||
mock_episode_manager.get_all_episodes.assert_called_once_with(
|
||||
offset=0, limit=20, anime_id=1, status='downloaded', sort_by='title', sort_order='desc'
|
||||
)
|
||||
|
||||
def test_get_episode_by_id_success(self, client, mock_session):
|
||||
"""Test GET /episodes/<id> - get specific episode."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode = {
|
||||
'id': 1,
|
||||
'anime_id': 1,
|
||||
'number': 1,
|
||||
'title': 'First Episode',
|
||||
'url': 'https://example.com/episode/1',
|
||||
'status': 'available',
|
||||
'duration': 1440,
|
||||
'description': 'The first episode'
|
||||
}
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = mock_episode
|
||||
|
||||
response = client.get('/api/v1/episodes/1')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert data['data']['id'] == 1
|
||||
assert data['data']['title'] == 'First Episode'
|
||||
|
||||
mock_episode_manager.get_episode_by_id.assert_called_once_with(1)
|
||||
|
||||
def test_get_episode_by_id_not_found(self, client, mock_session):
|
||||
"""Test GET /episodes/<id> - episode not found."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = None
|
||||
|
||||
response = client.get('/api/v1/episodes/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_episode_success(self, client, mock_session):
|
||||
"""Test POST /episodes - create new episode."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
episode_data = {
|
||||
'anime_id': 1,
|
||||
'number': 1,
|
||||
'title': 'New Episode',
|
||||
'url': 'https://example.com/new-episode',
|
||||
'duration': 1440,
|
||||
'description': 'A new episode'
|
||||
}
|
||||
|
||||
# Mock anime exists
|
||||
mock_anime_manager.get_anime_by_id.return_value = {'id': 1, 'name': 'Test Anime'}
|
||||
|
||||
mock_episode_manager.create_episode.return_value = 1
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
**episode_data
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/episodes',
|
||||
json=episode_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']['title'] == 'New Episode'
|
||||
|
||||
mock_episode_manager.create_episode.assert_called_once()
|
||||
|
||||
def test_create_episode_invalid_anime(self, client, mock_session):
|
||||
"""Test POST /episodes - invalid anime_id."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
episode_data = {
|
||||
'anime_id': 999,
|
||||
'number': 1,
|
||||
'title': 'New Episode',
|
||||
'url': 'https://example.com/new-episode'
|
||||
}
|
||||
|
||||
# Mock anime doesn't exist
|
||||
mock_anime_manager.get_anime_by_id.return_value = None
|
||||
|
||||
response = client.post('/api/v1/episodes',
|
||||
json=episode_data,
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 400
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
assert 'anime' in data['error'].lower()
|
||||
|
||||
def test_create_episode_validation_error(self, client, mock_session):
|
||||
"""Test POST /episodes - validation error."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
# Missing required fields
|
||||
episode_data = {
|
||||
'title': 'New Episode'
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/episodes',
|
||||
json=episode_data,
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 400
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
def test_update_episode_success(self, client, mock_session):
|
||||
"""Test PUT /episodes/<id> - update episode."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
update_data = {
|
||||
'title': 'Updated Episode',
|
||||
'description': 'Updated description',
|
||||
'status': 'downloaded'
|
||||
}
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
'title': 'Original Episode'
|
||||
}
|
||||
mock_episode_manager.update_episode.return_value = True
|
||||
|
||||
response = client.put('/api/v1/episodes/1',
|
||||
json=update_data,
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
|
||||
mock_episode_manager.update_episode.assert_called_once_with(1, update_data)
|
||||
|
||||
def test_update_episode_not_found(self, client, mock_session):
|
||||
"""Test PUT /episodes/<id> - episode not found."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = None
|
||||
|
||||
response = client.put('/api/v1/episodes/999',
|
||||
json={'title': 'Updated'},
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 404
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
def test_delete_episode_success(self, client, mock_session):
|
||||
"""Test DELETE /episodes/<id> - delete episode."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
'title': 'Test Episode'
|
||||
}
|
||||
mock_episode_manager.delete_episode.return_value = True
|
||||
|
||||
response = client.delete('/api/v1/episodes/1')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
|
||||
mock_episode_manager.delete_episode.assert_called_once_with(1)
|
||||
|
||||
def test_delete_episode_not_found(self, client, mock_session):
|
||||
"""Test DELETE /episodes/<id> - episode not found."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = None
|
||||
|
||||
response = client.delete('/api/v1/episodes/999')
|
||||
|
||||
assert response.status_code == 404
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
def test_bulk_create_episodes_success(self, client, mock_session):
|
||||
"""Test POST /episodes/bulk - bulk create episodes."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
bulk_data = {
|
||||
'episodes': [
|
||||
{'anime_id': 1, 'number': 1, 'title': 'Episode 1', 'url': 'https://example.com/1'},
|
||||
{'anime_id': 1, 'number': 2, 'title': 'Episode 2', 'url': 'https://example.com/2'}
|
||||
]
|
||||
}
|
||||
|
||||
mock_episode_manager.bulk_create_episodes.return_value = {
|
||||
'created': 2,
|
||||
'failed': 0,
|
||||
'created_ids': [1, 2]
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/episodes/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_status_success(self, client, mock_session):
|
||||
"""Test PUT /episodes/bulk/status - bulk update episode status."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
bulk_data = {
|
||||
'episode_ids': [1, 2, 3],
|
||||
'status': 'downloaded'
|
||||
}
|
||||
|
||||
mock_episode_manager.bulk_update_status.return_value = {
|
||||
'updated': 3,
|
||||
'failed': 0
|
||||
}
|
||||
|
||||
response = client.put('/api/v1/episodes/bulk/status',
|
||||
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'] == 3
|
||||
|
||||
def test_bulk_delete_episodes_success(self, client, mock_session):
|
||||
"""Test DELETE /episodes/bulk - bulk delete episodes."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
bulk_data = {
|
||||
'episode_ids': [1, 2, 3]
|
||||
}
|
||||
|
||||
mock_episode_manager.bulk_delete_episodes.return_value = {
|
||||
'deleted': 3,
|
||||
'failed': 0
|
||||
}
|
||||
|
||||
response = client.delete('/api/v1/episodes/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_sync_episodes_success(self, client, mock_session):
|
||||
"""Test POST /episodes/sync - sync episodes for anime."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
sync_data = {
|
||||
'anime_id': 1
|
||||
}
|
||||
|
||||
# Mock anime exists
|
||||
mock_anime_manager.get_anime_by_id.return_value = {'id': 1, 'name': 'Test Anime'}
|
||||
|
||||
mock_episode_manager.sync_episodes.return_value = {
|
||||
'anime_id': 1,
|
||||
'episodes_found': 12,
|
||||
'episodes_added': 5,
|
||||
'episodes_updated': 2
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/episodes/sync',
|
||||
json=sync_data,
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert data['data']['episodes_found'] == 12
|
||||
assert data['data']['episodes_added'] == 5
|
||||
|
||||
mock_episode_manager.sync_episodes.assert_called_once_with(1)
|
||||
|
||||
def test_sync_episodes_invalid_anime(self, client, mock_session):
|
||||
"""Test POST /episodes/sync - invalid anime_id."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
sync_data = {
|
||||
'anime_id': 999
|
||||
}
|
||||
|
||||
# Mock anime doesn't exist
|
||||
mock_anime_manager.get_anime_by_id.return_value = None
|
||||
|
||||
response = client.post('/api/v1/episodes/sync',
|
||||
json=sync_data,
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 400
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
assert 'anime' in data['error'].lower()
|
||||
|
||||
def test_get_episode_download_info_success(self, client, mock_session):
|
||||
"""Test GET /episodes/<id>/download - get episode download info."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
'title': 'Test Episode'
|
||||
}
|
||||
|
||||
mock_download_info = {
|
||||
'episode_id': 1,
|
||||
'download_id': 5,
|
||||
'status': 'downloading',
|
||||
'progress': 45.5,
|
||||
'speed': 1048576, # 1MB/s
|
||||
'eta': 300, # 5 minutes
|
||||
'file_path': '/downloads/episode1.mp4'
|
||||
}
|
||||
|
||||
mock_download_manager.get_episode_download_info.return_value = mock_download_info
|
||||
|
||||
response = client.get('/api/v1/episodes/1/download')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert data['data']['status'] == 'downloading'
|
||||
assert data['data']['progress'] == 45.5
|
||||
|
||||
def test_get_episode_download_info_not_downloading(self, client, mock_session):
|
||||
"""Test GET /episodes/<id>/download - episode not downloading."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
'title': 'Test Episode'
|
||||
}
|
||||
|
||||
mock_download_manager.get_episode_download_info.return_value = None
|
||||
|
||||
response = client.get('/api/v1/episodes/1/download')
|
||||
|
||||
assert response.status_code == 404
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
assert 'download' in data['error'].lower()
|
||||
|
||||
def test_start_episode_download_success(self, client, mock_session):
|
||||
"""Test POST /episodes/<id>/download - start episode download."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
'title': 'Test Episode',
|
||||
'url': 'https://example.com/episode/1'
|
||||
}
|
||||
|
||||
mock_download_manager.start_episode_download.return_value = {
|
||||
'download_id': 5,
|
||||
'status': 'queued',
|
||||
'message': 'Download queued successfully'
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/episodes/1/download')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
assert data['data']['download_id'] == 5
|
||||
assert data['data']['status'] == 'queued'
|
||||
|
||||
mock_download_manager.start_episode_download.assert_called_once_with(1)
|
||||
|
||||
def test_cancel_episode_download_success(self, client, mock_session):
|
||||
"""Test DELETE /episodes/<id>/download - cancel episode download."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
'title': 'Test Episode'
|
||||
}
|
||||
|
||||
mock_download_manager.cancel_episode_download.return_value = True
|
||||
|
||||
response = client.delete('/api/v1/episodes/1/download')
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['success'] is True
|
||||
|
||||
mock_download_manager.cancel_episode_download.assert_called_once_with(1)
|
||||
|
||||
def test_get_episodes_by_anime_success(self, client, mock_session):
|
||||
"""Test GET /anime/<anime_id>/episodes - get episodes for anime."""
|
||||
if not episodes_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_episode_manager.get_episodes_by_anime.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
|
||||
|
||||
mock_episode_manager.get_episodes_by_anime.assert_called_once_with(1)
|
||||
|
||||
|
||||
class TestEpisodeAuthentication:
|
||||
"""Test cases for episode endpoints authentication."""
|
||||
|
||||
@pytest.fixture
|
||||
def app(self):
|
||||
"""Create a test Flask application."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['TESTING'] = True
|
||||
app.register_blueprint(episodes_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 episodes_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_episode_manager.get_all_episodes.return_value = []
|
||||
mock_episode_manager.get_episodes_count.return_value = 0
|
||||
|
||||
response = client.get('/api/v1/episodes')
|
||||
# Should work for read operations
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_authenticated_write_access(self, client):
|
||||
"""Test that write operations require authentication."""
|
||||
if not episodes_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/episodes',
|
||||
json={'title': 'Test'},
|
||||
content_type='application/json')
|
||||
# Should require authentication for write operations
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
class TestEpisodeErrorHandling:
|
||||
"""Test cases for episode endpoints error handling."""
|
||||
|
||||
@pytest.fixture
|
||||
def app(self):
|
||||
"""Create a test Flask application."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['TESTING'] = True
|
||||
app.register_blueprint(episodes_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 episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
# Simulate database error
|
||||
mock_episode_manager.get_all_episodes.side_effect = Exception("Database connection failed")
|
||||
|
||||
response = client.get('/api/v1/episodes')
|
||||
|
||||
assert response.status_code == 500
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
def test_invalid_episode_id_parameter(self, client, mock_session):
|
||||
"""Test handling of invalid episode ID parameter."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
response = client.get('/api/v1/episodes/invalid-id')
|
||||
|
||||
assert response.status_code == 404 # Flask will handle this as route not found
|
||||
|
||||
def test_concurrent_modification_error(self, client, mock_session):
|
||||
"""Test handling of concurrent modification errors."""
|
||||
if not episodes_bp:
|
||||
pytest.skip("Module not available")
|
||||
|
||||
mock_episode_manager.get_episode_by_id.return_value = {
|
||||
'id': 1,
|
||||
'title': 'Test Episode'
|
||||
}
|
||||
|
||||
# Simulate concurrent modification
|
||||
mock_episode_manager.update_episode.side_effect = Exception("Episode was modified by another process")
|
||||
|
||||
response = client.put('/api/v1/episodes/1',
|
||||
json={'title': 'Updated'},
|
||||
content_type='application/json')
|
||||
|
||||
assert response.status_code == 500
|
||||
data = json.loads(response.data)
|
||||
assert 'error' in data
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__])
|
||||
Reference in New Issue
Block a user