|
|
"""Unit tests for utility functions.""" |
|
|
import pytest |
|
|
from app_utils import validate_youtube_url, check_video_availability |
|
|
from unittest.mock import patch, MagicMock |
|
|
import yt_dlp |
|
|
|
|
|
|
|
|
class TestValidateYouTubeURL: |
|
|
"""Test YouTube URL validation.""" |
|
|
|
|
|
def test_valid_watch_url(self): |
|
|
"""Test standard youtube.com/watch URL.""" |
|
|
is_valid, video_id = validate_youtube_url("https://www.youtube.com/watch?v=dQw4w9WgXcQ") |
|
|
assert is_valid is True |
|
|
assert video_id == "dQw4w9WgXcQ" |
|
|
|
|
|
def test_valid_short_url(self): |
|
|
"""Test youtu.be short URL.""" |
|
|
is_valid, video_id = validate_youtube_url("https://youtu.be/dQw4w9WgXcQ") |
|
|
assert is_valid is True |
|
|
assert video_id == "dQw4w9WgXcQ" |
|
|
|
|
|
def test_valid_mobile_url(self): |
|
|
"""Test mobile YouTube URL.""" |
|
|
is_valid, video_id = validate_youtube_url("https://m.youtube.com/watch?v=dQw4w9WgXcQ") |
|
|
assert is_valid is True |
|
|
assert video_id == "dQw4w9WgXcQ" |
|
|
|
|
|
def test_valid_embed_url(self): |
|
|
"""Test embedded YouTube URL.""" |
|
|
is_valid, video_id = validate_youtube_url("https://www.youtube.com/embed/dQw4w9WgXcQ") |
|
|
assert is_valid is True |
|
|
assert video_id == "dQw4w9WgXcQ" |
|
|
|
|
|
def test_valid_with_extra_params(self): |
|
|
"""Test URL with additional query parameters.""" |
|
|
is_valid, video_id = validate_youtube_url("https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=30s") |
|
|
assert is_valid is True |
|
|
assert video_id == "dQw4w9WgXcQ" |
|
|
|
|
|
def test_invalid_domain(self): |
|
|
"""Test URL from wrong domain.""" |
|
|
is_valid, error = validate_youtube_url("https://vimeo.com/12345") |
|
|
assert is_valid is False |
|
|
assert error == "Invalid YouTube URL format" |
|
|
|
|
|
def test_invalid_format(self): |
|
|
"""Test malformed URL.""" |
|
|
is_valid, error = validate_youtube_url("not-a-url") |
|
|
assert is_valid is False |
|
|
assert error == "Invalid YouTube URL format" |
|
|
|
|
|
def test_invalid_video_id_length(self): |
|
|
"""Test URL with incorrect video ID length.""" |
|
|
is_valid, error = validate_youtube_url("https://www.youtube.com/watch?v=short") |
|
|
assert is_valid is False |
|
|
assert error == "Invalid YouTube URL format" |
|
|
|
|
|
def test_empty_url(self): |
|
|
"""Test empty URL.""" |
|
|
is_valid, error = validate_youtube_url("") |
|
|
assert is_valid is False |
|
|
assert error == "Invalid YouTube URL format" |
|
|
|
|
|
|
|
|
class TestCheckVideoAvailability: |
|
|
"""Test video availability checking.""" |
|
|
|
|
|
@patch('yt_dlp.YoutubeDL') |
|
|
def test_available_video(self, mock_ydl_class, mock_yt_dlp_info): |
|
|
"""Test checking available video.""" |
|
|
mock_ydl = MagicMock() |
|
|
mock_ydl.extract_info.return_value = mock_yt_dlp_info |
|
|
mock_ydl_class.return_value.__enter__.return_value = mock_ydl |
|
|
|
|
|
result = check_video_availability("dQw4w9WgXcQ") |
|
|
|
|
|
assert result['available'] is True |
|
|
assert 'info' in result |
|
|
|
|
|
@patch('yt_dlp.YoutubeDL') |
|
|
def test_video_too_long(self, mock_ydl_class): |
|
|
"""Test video exceeding duration limit.""" |
|
|
mock_ydl = MagicMock() |
|
|
mock_ydl.extract_info.return_value = { |
|
|
'duration': 1200, |
|
|
'age_limit': 0 |
|
|
} |
|
|
mock_ydl_class.return_value.__enter__.return_value = mock_ydl |
|
|
|
|
|
result = check_video_availability("dQw4w9WgXcQ", max_duration=900) |
|
|
|
|
|
assert result['available'] is False |
|
|
assert 'max 15 minutes' in result['reason'] |
|
|
|
|
|
@patch('yt_dlp.YoutubeDL') |
|
|
def test_age_restricted_video(self, mock_ydl_class): |
|
|
"""Test age-restricted video.""" |
|
|
mock_ydl = MagicMock() |
|
|
mock_ydl.extract_info.return_value = { |
|
|
'duration': 180, |
|
|
'age_limit': 18 |
|
|
} |
|
|
mock_ydl_class.return_value.__enter__.return_value = mock_ydl |
|
|
|
|
|
result = check_video_availability("dQw4w9WgXcQ") |
|
|
|
|
|
assert result['available'] is False |
|
|
assert 'Age-restricted' in result['reason'] |
|
|
|
|
|
@patch('yt_dlp.YoutubeDL') |
|
|
def test_download_error(self, mock_ydl_class): |
|
|
"""Test yt-dlp download error.""" |
|
|
mock_ydl = MagicMock() |
|
|
mock_ydl.extract_info.side_effect = yt_dlp.utils.DownloadError("Video unavailable") |
|
|
mock_ydl_class.return_value.__enter__.return_value = mock_ydl |
|
|
|
|
|
result = check_video_availability("invalid_id") |
|
|
|
|
|
assert result['available'] is False |
|
|
assert 'Video unavailable' in result['reason'] |
|
|
|
|
|
@patch('yt_dlp.YoutubeDL') |
|
|
def test_generic_error(self, mock_ydl_class): |
|
|
"""Test generic error handling.""" |
|
|
mock_ydl = MagicMock() |
|
|
mock_ydl.extract_info.side_effect = Exception("Unknown error") |
|
|
mock_ydl_class.return_value.__enter__.return_value = mock_ydl |
|
|
|
|
|
result = check_video_availability("dQw4w9WgXcQ") |
|
|
|
|
|
assert result['available'] is False |
|
|
assert 'Error checking video' in result['reason'] |
|
|
|
|
|
@patch('yt_dlp.YoutubeDL') |
|
|
def test_video_at_max_duration(self, mock_ydl_class): |
|
|
"""Test video exactly at duration limit.""" |
|
|
mock_ydl = MagicMock() |
|
|
mock_ydl.extract_info.return_value = { |
|
|
'duration': 900, |
|
|
'age_limit': 0 |
|
|
} |
|
|
mock_ydl_class.return_value.__enter__.return_value = mock_ydl |
|
|
|
|
|
result = check_video_availability("dQw4w9WgXcQ", max_duration=900) |
|
|
|
|
|
assert result['available'] is True |
|
|
|