560 lines
20 KiB
Python
560 lines
20 KiB
Python
"""
|
|
Test cases for response helper utilities.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
import json
|
|
from datetime import datetime
|
|
|
|
# Import the modules to test
|
|
try:
|
|
from src.server.web.controllers.shared.response_helpers import (
|
|
create_response, create_error_response, create_success_response,
|
|
create_paginated_response, format_anime_data, format_episode_data,
|
|
format_download_data, format_user_data, format_datetime, format_file_size,
|
|
add_cors_headers, create_api_response
|
|
)
|
|
except ImportError:
|
|
# Fallback for testing
|
|
create_response = None
|
|
create_error_response = None
|
|
create_success_response = None
|
|
create_paginated_response = None
|
|
format_anime_data = None
|
|
format_episode_data = None
|
|
format_download_data = None
|
|
format_user_data = None
|
|
format_datetime = None
|
|
format_file_size = None
|
|
add_cors_headers = None
|
|
create_api_response = None
|
|
|
|
|
|
class TestResponseCreation:
|
|
"""Test cases for response creation functions."""
|
|
|
|
def test_create_response_success(self):
|
|
"""Test create_response with success data."""
|
|
if not create_response:
|
|
pytest.skip("Module not available")
|
|
|
|
data = {'test': 'data'}
|
|
response, status_code = create_response(data, 200)
|
|
|
|
assert status_code == 200
|
|
response_data = json.loads(response.data)
|
|
assert response_data['test'] == 'data'
|
|
assert response.status_code == 200
|
|
|
|
def test_create_response_with_headers(self):
|
|
"""Test create_response with custom headers."""
|
|
if not create_response:
|
|
pytest.skip("Module not available")
|
|
|
|
data = {'test': 'data'}
|
|
headers = {'X-Custom-Header': 'test-value'}
|
|
response, status_code = create_response(data, 200, headers)
|
|
|
|
assert response.headers.get('X-Custom-Header') == 'test-value'
|
|
|
|
def test_create_error_response_basic(self):
|
|
"""Test create_error_response with basic error."""
|
|
if not create_error_response:
|
|
pytest.skip("Module not available")
|
|
|
|
response, status_code = create_error_response("Test error", 400)
|
|
|
|
assert status_code == 400
|
|
response_data = json.loads(response.data)
|
|
assert response_data['error'] == 'Test error'
|
|
assert response_data['status'] == 'error'
|
|
|
|
def test_create_error_response_with_details(self):
|
|
"""Test create_error_response with error details."""
|
|
if not create_error_response:
|
|
pytest.skip("Module not available")
|
|
|
|
details = {'field': 'name', 'issue': 'required'}
|
|
response, status_code = create_error_response("Validation error", 422, details)
|
|
|
|
assert status_code == 422
|
|
response_data = json.loads(response.data)
|
|
assert response_data['error'] == 'Validation error'
|
|
assert response_data['details'] == details
|
|
|
|
def test_create_success_response_basic(self):
|
|
"""Test create_success_response with basic success."""
|
|
if not create_success_response:
|
|
pytest.skip("Module not available")
|
|
|
|
response, status_code = create_success_response("Operation successful")
|
|
|
|
assert status_code == 200
|
|
response_data = json.loads(response.data)
|
|
assert response_data['message'] == 'Operation successful'
|
|
assert response_data['status'] == 'success'
|
|
|
|
def test_create_success_response_with_data(self):
|
|
"""Test create_success_response with data."""
|
|
if not create_success_response:
|
|
pytest.skip("Module not available")
|
|
|
|
data = {'created_id': 123}
|
|
response, status_code = create_success_response("Created successfully", 201, data)
|
|
|
|
assert status_code == 201
|
|
response_data = json.loads(response.data)
|
|
assert response_data['message'] == 'Created successfully'
|
|
assert response_data['data'] == data
|
|
|
|
def test_create_api_response_success(self):
|
|
"""Test create_api_response for success case."""
|
|
if not create_api_response:
|
|
pytest.skip("Module not available")
|
|
|
|
data = {'test': 'data'}
|
|
response, status_code = create_api_response(data, success=True)
|
|
|
|
assert status_code == 200
|
|
response_data = json.loads(response.data)
|
|
assert response_data['success'] is True
|
|
assert response_data['data'] == data
|
|
|
|
def test_create_api_response_error(self):
|
|
"""Test create_api_response for error case."""
|
|
if not create_api_response:
|
|
pytest.skip("Module not available")
|
|
|
|
error_msg = "Something went wrong"
|
|
response, status_code = create_api_response(error_msg, success=False, status_code=500)
|
|
|
|
assert status_code == 500
|
|
response_data = json.loads(response.data)
|
|
assert response_data['success'] is False
|
|
assert response_data['error'] == error_msg
|
|
|
|
|
|
class TestPaginatedResponse:
|
|
"""Test cases for paginated response creation."""
|
|
|
|
def test_create_paginated_response_basic(self):
|
|
"""Test create_paginated_response with basic pagination."""
|
|
if not create_paginated_response:
|
|
pytest.skip("Module not available")
|
|
|
|
items = [{'id': 1}, {'id': 2}, {'id': 3}]
|
|
page = 1
|
|
per_page = 10
|
|
total = 25
|
|
|
|
response, status_code = create_paginated_response(items, page, per_page, total)
|
|
|
|
assert status_code == 200
|
|
response_data = json.loads(response.data)
|
|
assert response_data['data'] == items
|
|
assert response_data['pagination']['page'] == 1
|
|
assert response_data['pagination']['per_page'] == 10
|
|
assert response_data['pagination']['total'] == 25
|
|
assert response_data['pagination']['pages'] == 3 # ceil(25/10)
|
|
|
|
def test_create_paginated_response_with_endpoint(self):
|
|
"""Test create_paginated_response with endpoint for links."""
|
|
if not create_paginated_response:
|
|
pytest.skip("Module not available")
|
|
|
|
items = [{'id': 1}]
|
|
page = 2
|
|
per_page = 5
|
|
total = 20
|
|
endpoint = '/api/items'
|
|
|
|
response, status_code = create_paginated_response(
|
|
items, page, per_page, total, endpoint=endpoint
|
|
)
|
|
|
|
response_data = json.loads(response.data)
|
|
links = response_data['pagination']['links']
|
|
assert '/api/items?page=1' in links['first']
|
|
assert '/api/items?page=3' in links['next']
|
|
assert '/api/items?page=1' in links['prev']
|
|
assert '/api/items?page=4' in links['last']
|
|
|
|
def test_create_paginated_response_first_page(self):
|
|
"""Test create_paginated_response on first page."""
|
|
if not create_paginated_response:
|
|
pytest.skip("Module not available")
|
|
|
|
items = [{'id': 1}]
|
|
response, status_code = create_paginated_response(items, 1, 10, 20)
|
|
|
|
response_data = json.loads(response.data)
|
|
pagination = response_data['pagination']
|
|
assert pagination['has_prev'] is False
|
|
assert pagination['has_next'] is True
|
|
|
|
def test_create_paginated_response_last_page(self):
|
|
"""Test create_paginated_response on last page."""
|
|
if not create_paginated_response:
|
|
pytest.skip("Module not available")
|
|
|
|
items = [{'id': 1}]
|
|
response, status_code = create_paginated_response(items, 3, 10, 25)
|
|
|
|
response_data = json.loads(response.data)
|
|
pagination = response_data['pagination']
|
|
assert pagination['has_prev'] is True
|
|
assert pagination['has_next'] is False
|
|
|
|
def test_create_paginated_response_empty(self):
|
|
"""Test create_paginated_response with empty results."""
|
|
if not create_paginated_response:
|
|
pytest.skip("Module not available")
|
|
|
|
response, status_code = create_paginated_response([], 1, 10, 0)
|
|
|
|
response_data = json.loads(response.data)
|
|
assert response_data['data'] == []
|
|
assert response_data['pagination']['total'] == 0
|
|
assert response_data['pagination']['pages'] == 0
|
|
|
|
|
|
class TestDataFormatting:
|
|
"""Test cases for data formatting functions."""
|
|
|
|
def test_format_anime_data(self):
|
|
"""Test format_anime_data function."""
|
|
if not format_anime_data:
|
|
pytest.skip("Module not available")
|
|
|
|
anime = {
|
|
'id': 1,
|
|
'name': 'Test Anime',
|
|
'url': 'https://example.com/anime/1',
|
|
'description': 'A test anime',
|
|
'episodes': 12,
|
|
'status': 'completed',
|
|
'created_at': '2023-01-01 12:00:00',
|
|
'updated_at': '2023-01-02 12:00:00'
|
|
}
|
|
|
|
formatted = format_anime_data(anime)
|
|
|
|
assert formatted['id'] == 1
|
|
assert formatted['name'] == 'Test Anime'
|
|
assert formatted['url'] == 'https://example.com/anime/1'
|
|
assert formatted['description'] == 'A test anime'
|
|
assert formatted['episodes'] == 12
|
|
assert formatted['status'] == 'completed'
|
|
assert 'created_at' in formatted
|
|
assert 'updated_at' in formatted
|
|
|
|
def test_format_anime_data_with_episodes(self):
|
|
"""Test format_anime_data with episode information."""
|
|
if not format_anime_data:
|
|
pytest.skip("Module not available")
|
|
|
|
anime = {
|
|
'id': 1,
|
|
'name': 'Test Anime',
|
|
'url': 'https://example.com/anime/1'
|
|
}
|
|
|
|
episodes = [
|
|
{'id': 1, 'number': 1, 'title': 'Episode 1'},
|
|
{'id': 2, 'number': 2, 'title': 'Episode 2'}
|
|
]
|
|
|
|
formatted = format_anime_data(anime, include_episodes=True, episodes=episodes)
|
|
|
|
assert 'episodes' in formatted
|
|
assert len(formatted['episodes']) == 2
|
|
assert formatted['episodes'][0]['number'] == 1
|
|
|
|
def test_format_episode_data(self):
|
|
"""Test format_episode_data function."""
|
|
if not format_episode_data:
|
|
pytest.skip("Module not available")
|
|
|
|
episode = {
|
|
'id': 1,
|
|
'anime_id': 5,
|
|
'number': 1,
|
|
'title': 'First Episode',
|
|
'url': 'https://example.com/episode/1',
|
|
'duration': 1440, # 24 minutes in seconds
|
|
'status': 'available',
|
|
'created_at': '2023-01-01 12:00:00'
|
|
}
|
|
|
|
formatted = format_episode_data(episode)
|
|
|
|
assert formatted['id'] == 1
|
|
assert formatted['anime_id'] == 5
|
|
assert formatted['number'] == 1
|
|
assert formatted['title'] == 'First Episode'
|
|
assert formatted['url'] == 'https://example.com/episode/1'
|
|
assert formatted['duration'] == 1440
|
|
assert formatted['status'] == 'available'
|
|
assert 'created_at' in formatted
|
|
|
|
def test_format_download_data(self):
|
|
"""Test format_download_data function."""
|
|
if not format_download_data:
|
|
pytest.skip("Module not available")
|
|
|
|
download = {
|
|
'id': 1,
|
|
'anime_id': 5,
|
|
'episode_id': 10,
|
|
'status': 'downloading',
|
|
'progress': 75.5,
|
|
'size': 1073741824, # 1GB in bytes
|
|
'downloaded_size': 805306368, # 768MB
|
|
'speed': 1048576, # 1MB/s
|
|
'eta': 300, # 5 minutes
|
|
'created_at': '2023-01-01 12:00:00',
|
|
'started_at': '2023-01-01 12:05:00'
|
|
}
|
|
|
|
formatted = format_download_data(download)
|
|
|
|
assert formatted['id'] == 1
|
|
assert formatted['anime_id'] == 5
|
|
assert formatted['episode_id'] == 10
|
|
assert formatted['status'] == 'downloading'
|
|
assert formatted['progress'] == 75.5
|
|
assert formatted['size'] == 1073741824
|
|
assert formatted['downloaded_size'] == 805306368
|
|
assert formatted['speed'] == 1048576
|
|
assert formatted['eta'] == 300
|
|
assert 'created_at' in formatted
|
|
assert 'started_at' in formatted
|
|
|
|
def test_format_user_data(self):
|
|
"""Test format_user_data function."""
|
|
if not format_user_data:
|
|
pytest.skip("Module not available")
|
|
|
|
user = {
|
|
'id': 1,
|
|
'username': 'testuser',
|
|
'email': 'test@example.com',
|
|
'password_hash': 'secret_hash',
|
|
'role': 'user',
|
|
'last_login': '2023-01-01 12:00:00',
|
|
'created_at': '2023-01-01 10:00:00'
|
|
}
|
|
|
|
formatted = format_user_data(user)
|
|
|
|
assert formatted['id'] == 1
|
|
assert formatted['username'] == 'testuser'
|
|
assert formatted['email'] == 'test@example.com'
|
|
assert formatted['role'] == 'user'
|
|
assert 'last_login' in formatted
|
|
assert 'created_at' in formatted
|
|
# Should not include sensitive data
|
|
assert 'password_hash' not in formatted
|
|
|
|
def test_format_user_data_include_sensitive(self):
|
|
"""Test format_user_data with sensitive data included."""
|
|
if not format_user_data:
|
|
pytest.skip("Module not available")
|
|
|
|
user = {
|
|
'id': 1,
|
|
'username': 'testuser',
|
|
'password_hash': 'secret_hash'
|
|
}
|
|
|
|
formatted = format_user_data(user, include_sensitive=True)
|
|
|
|
assert 'password_hash' in formatted
|
|
assert formatted['password_hash'] == 'secret_hash'
|
|
|
|
|
|
class TestUtilityFormatting:
|
|
"""Test cases for utility formatting functions."""
|
|
|
|
def test_format_datetime_string(self):
|
|
"""Test format_datetime with string input."""
|
|
if not format_datetime:
|
|
pytest.skip("Module not available")
|
|
|
|
dt_string = "2023-01-01 12:30:45"
|
|
formatted = format_datetime(dt_string)
|
|
|
|
assert isinstance(formatted, str)
|
|
assert "2023" in formatted
|
|
assert "01" in formatted
|
|
|
|
def test_format_datetime_object(self):
|
|
"""Test format_datetime with datetime object."""
|
|
if not format_datetime:
|
|
pytest.skip("Module not available")
|
|
|
|
dt_object = datetime(2023, 1, 1, 12, 30, 45)
|
|
formatted = format_datetime(dt_object)
|
|
|
|
assert isinstance(formatted, str)
|
|
assert "2023" in formatted
|
|
|
|
def test_format_datetime_none(self):
|
|
"""Test format_datetime with None input."""
|
|
if not format_datetime:
|
|
pytest.skip("Module not available")
|
|
|
|
formatted = format_datetime(None)
|
|
assert formatted is None
|
|
|
|
def test_format_datetime_custom_format(self):
|
|
"""Test format_datetime with custom format."""
|
|
if not format_datetime:
|
|
pytest.skip("Module not available")
|
|
|
|
dt_string = "2023-01-01 12:30:45"
|
|
formatted = format_datetime(dt_string, fmt="%Y/%m/%d")
|
|
|
|
assert formatted == "2023/01/01"
|
|
|
|
def test_format_file_size_bytes(self):
|
|
"""Test format_file_size with bytes."""
|
|
if not format_file_size:
|
|
pytest.skip("Module not available")
|
|
|
|
assert format_file_size(512) == "512 B"
|
|
assert format_file_size(0) == "0 B"
|
|
|
|
def test_format_file_size_kilobytes(self):
|
|
"""Test format_file_size with kilobytes."""
|
|
if not format_file_size:
|
|
pytest.skip("Module not available")
|
|
|
|
assert format_file_size(1024) == "1.0 KB"
|
|
assert format_file_size(1536) == "1.5 KB"
|
|
|
|
def test_format_file_size_megabytes(self):
|
|
"""Test format_file_size with megabytes."""
|
|
if not format_file_size:
|
|
pytest.skip("Module not available")
|
|
|
|
assert format_file_size(1048576) == "1.0 MB"
|
|
assert format_file_size(1572864) == "1.5 MB"
|
|
|
|
def test_format_file_size_gigabytes(self):
|
|
"""Test format_file_size with gigabytes."""
|
|
if not format_file_size:
|
|
pytest.skip("Module not available")
|
|
|
|
assert format_file_size(1073741824) == "1.0 GB"
|
|
assert format_file_size(2147483648) == "2.0 GB"
|
|
|
|
def test_format_file_size_terabytes(self):
|
|
"""Test format_file_size with terabytes."""
|
|
if not format_file_size:
|
|
pytest.skip("Module not available")
|
|
|
|
assert format_file_size(1099511627776) == "1.0 TB"
|
|
|
|
def test_format_file_size_precision(self):
|
|
"""Test format_file_size with custom precision."""
|
|
if not format_file_size:
|
|
pytest.skip("Module not available")
|
|
|
|
size = 1536 # 1.5 KB
|
|
assert format_file_size(size, precision=2) == "1.50 KB"
|
|
assert format_file_size(size, precision=0) == "2 KB" # Rounded up
|
|
|
|
|
|
class TestCORSHeaders:
|
|
"""Test cases for CORS header utilities."""
|
|
|
|
def test_add_cors_headers_basic(self):
|
|
"""Test add_cors_headers with basic response."""
|
|
if not add_cors_headers:
|
|
pytest.skip("Module not available")
|
|
|
|
# Mock response object
|
|
response = Mock()
|
|
response.headers = {}
|
|
|
|
result = add_cors_headers(response)
|
|
|
|
assert result.headers['Access-Control-Allow-Origin'] == '*'
|
|
assert 'GET, POST, PUT, DELETE, OPTIONS' in result.headers['Access-Control-Allow-Methods']
|
|
assert 'Content-Type, Authorization' in result.headers['Access-Control-Allow-Headers']
|
|
|
|
def test_add_cors_headers_custom_origin(self):
|
|
"""Test add_cors_headers with custom origin."""
|
|
if not add_cors_headers:
|
|
pytest.skip("Module not available")
|
|
|
|
response = Mock()
|
|
response.headers = {}
|
|
|
|
result = add_cors_headers(response, origin='https://example.com')
|
|
|
|
assert result.headers['Access-Control-Allow-Origin'] == 'https://example.com'
|
|
|
|
def test_add_cors_headers_custom_methods(self):
|
|
"""Test add_cors_headers with custom methods."""
|
|
if not add_cors_headers:
|
|
pytest.skip("Module not available")
|
|
|
|
response = Mock()
|
|
response.headers = {}
|
|
|
|
result = add_cors_headers(response, methods=['GET', 'POST'])
|
|
|
|
assert result.headers['Access-Control-Allow-Methods'] == 'GET, POST'
|
|
|
|
def test_add_cors_headers_existing_headers(self):
|
|
"""Test add_cors_headers preserves existing headers."""
|
|
if not add_cors_headers:
|
|
pytest.skip("Module not available")
|
|
|
|
response = Mock()
|
|
response.headers = {'X-Custom-Header': 'custom-value'}
|
|
|
|
result = add_cors_headers(response)
|
|
|
|
assert result.headers['X-Custom-Header'] == 'custom-value'
|
|
assert 'Access-Control-Allow-Origin' in result.headers
|
|
|
|
|
|
class TestResponseIntegration:
|
|
"""Integration tests for response helpers."""
|
|
|
|
def test_formatted_paginated_response(self):
|
|
"""Test creating paginated response with formatted data."""
|
|
if not create_paginated_response or not format_anime_data:
|
|
pytest.skip("Module not available")
|
|
|
|
anime_list = [
|
|
{'id': 1, 'name': 'Anime 1', 'url': 'https://example.com/1'},
|
|
{'id': 2, 'name': 'Anime 2', 'url': 'https://example.com/2'}
|
|
]
|
|
|
|
formatted_items = [format_anime_data(anime) for anime in anime_list]
|
|
response, status_code = create_paginated_response(formatted_items, 1, 10, 2)
|
|
|
|
assert status_code == 200
|
|
response_data = json.loads(response.data)
|
|
assert len(response_data['data']) == 2
|
|
assert response_data['data'][0]['name'] == 'Anime 1'
|
|
|
|
def test_error_response_with_cors(self):
|
|
"""Test error response with CORS headers."""
|
|
if not create_error_response or not add_cors_headers:
|
|
pytest.skip("Module not available")
|
|
|
|
response, status_code = create_error_response("Test error", 400)
|
|
response_with_cors = add_cors_headers(response)
|
|
|
|
assert 'Access-Control-Allow-Origin' in response_with_cors.headers
|
|
assert status_code == 400
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__]) |