- Add exponential backoff retry logic to RecoveryStrategies (1s, 2s, 4s...) - Add TimeoutError to network failure handling for HTTPS timeouts - Add playmogo.com referer header for Doodstream provider - Add Optional import to error_handler.py - Add sanitize_url_for_logging utility function Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
293 lines
10 KiB
Python
293 lines
10 KiB
Python
"""
|
|
Unit tests for key generation utilities.
|
|
"""
|
|
|
|
import pytest
|
|
from src.core.utils.key_utils import (
|
|
generate_key_from_folder,
|
|
normalize_key,
|
|
is_valid_key,
|
|
sanitize_key_for_url,
|
|
validate_key_uniqueness,
|
|
)
|
|
|
|
|
|
class TestGenerateKeyFromFolder:
|
|
"""Test generate_key_from_folder function with edge cases."""
|
|
|
|
def test_standard_folder_name(self):
|
|
"""Test standard folder name with year."""
|
|
key = generate_key_from_folder("Attack on Titan (2013)")
|
|
assert key == "attack-on-titan-2013"
|
|
assert is_valid_key(key)
|
|
|
|
def test_a_time_called_you(self):
|
|
"""Test 'A Time Called You (2023)' - the specific failing case."""
|
|
key = generate_key_from_folder("A Time Called You (2023)")
|
|
assert key is not None
|
|
assert key != ""
|
|
assert is_valid_key(key)
|
|
|
|
def test_andor_2022(self):
|
|
"""Test 'Andor (2022)' - the specific failing case."""
|
|
key = generate_key_from_folder("Andor (2022)")
|
|
assert key is not None
|
|
assert key != ""
|
|
assert is_valid_key(key)
|
|
|
|
def test_japanese_mixed_folder(self):
|
|
"""Test '25-sai no Joshikousei (2018)' - Japanese + Latin mixed."""
|
|
key = generate_key_from_folder("25-sai no Joshikousei (2018)")
|
|
assert key is not None
|
|
assert key != ""
|
|
assert is_valid_key(key)
|
|
|
|
def test_folder_with_only_special_characters(self):
|
|
"""Test folder that would slugify to empty string."""
|
|
key = generate_key_from_folder("!!!@@@###")
|
|
assert key is not None
|
|
assert key != ""
|
|
# Should use UUID fallback
|
|
|
|
def test_folder_with_only_numbers(self):
|
|
"""Test folder that is just numbers."""
|
|
key = generate_key_from_folder("12345")
|
|
assert key is not None
|
|
assert key != ""
|
|
assert is_valid_key(key)
|
|
|
|
def test_folder_with_parentheses_and_year(self):
|
|
"""Test folder with parentheses containing year."""
|
|
key = generate_key_from_folder("My Series (2020)")
|
|
assert key is not None
|
|
assert key != ""
|
|
assert is_valid_key(key)
|
|
|
|
def test_folder_with_brackets(self):
|
|
"""Test folder with square brackets."""
|
|
key = generate_key_from_folder("My Series [Special] (2021)")
|
|
assert key is not None
|
|
assert key != ""
|
|
assert is_valid_key(key)
|
|
|
|
def test_unicode_characters(self):
|
|
"""Test folder with various Unicode characters."""
|
|
key = generate_key_from_folder("Héros Légende (2022)")
|
|
assert key is not None
|
|
assert key != ""
|
|
assert is_valid_key(key)
|
|
|
|
def test_korean_characters(self):
|
|
"""Test folder with Korean characters."""
|
|
key = generate_key_from_folder("나의 애니메이션 (2023)")
|
|
assert key is not None
|
|
assert key != ""
|
|
|
|
def test_chinese_characters(self):
|
|
"""Test folder with Chinese characters."""
|
|
key = generate_key_from_folder("我的动漫 (2024)")
|
|
assert key is not None
|
|
assert key != ""
|
|
|
|
def test_empty_string_input(self):
|
|
"""Test empty string input raises ValueError."""
|
|
with pytest.raises(ValueError, match="Folder name cannot be empty"):
|
|
generate_key_from_folder("")
|
|
|
|
def test_only_whitespace_input(self):
|
|
"""Test whitespace-only input raises ValueError."""
|
|
with pytest.raises(ValueError, match="Folder name cannot be empty"):
|
|
generate_key_from_folder(" ")
|
|
|
|
def test_single_character_folder(self):
|
|
"""Test single character folder name."""
|
|
key = generate_key_from_folder("X")
|
|
assert key is not None
|
|
assert key != ""
|
|
assert is_valid_key(key)
|
|
|
|
def test_very_long_folder_name(self):
|
|
"""Test very long folder name."""
|
|
long_name = "A" * 200
|
|
key = generate_key_from_folder(long_name)
|
|
assert key is not None
|
|
assert key != ""
|
|
|
|
def test_multiple_spaces(self):
|
|
"""Test folder with multiple consecutive spaces."""
|
|
key = generate_key_from_folder("My Series Name")
|
|
assert key is not None
|
|
assert key != ""
|
|
|
|
def test_leading_trailing_spaces(self):
|
|
"""Test folder with leading and trailing spaces."""
|
|
key = generate_key_from_folder(" My Series ")
|
|
assert key is not None
|
|
assert key != ""
|
|
|
|
def test_diacritics_normalization(self):
|
|
"""Test that diacritics are properly normalized."""
|
|
key = generate_key_from_folder("Animé (2023)")
|
|
assert key is not None
|
|
assert is_valid_key(key)
|
|
|
|
|
|
class TestNormalizeKey:
|
|
"""Test normalize_key function."""
|
|
|
|
def test_normalize_standard_key(self):
|
|
"""Test normalizing a standard key."""
|
|
result = normalize_key("Attack-on-Titan")
|
|
assert result == "attack-on-titan"
|
|
|
|
def test_normalize_with_underscores(self):
|
|
"""Test normalizing key with underscores."""
|
|
result = normalize_key("attack_on_titan")
|
|
assert result == "attack-on-titan"
|
|
|
|
def test_normalize_mixed_case(self):
|
|
"""Test normalizing mixed case key."""
|
|
result = normalize_key("Attack_On_Titan")
|
|
assert result == "attack-on-titan"
|
|
|
|
def test_normalize_with_spaces(self):
|
|
"""Test normalizing key with spaces."""
|
|
result = normalize_key("attack on titan")
|
|
assert result == "attack-on-titan"
|
|
|
|
def test_normalize_empty_string(self):
|
|
"""Test normalizing empty string returns empty."""
|
|
result = normalize_key("")
|
|
assert result == ""
|
|
|
|
def test_normalize_only_special_chars(self):
|
|
"""Test normalizing string with only special characters."""
|
|
result = normalize_key("!!!@@@")
|
|
assert result == ""
|
|
|
|
|
|
class TestIsValidKey:
|
|
"""Test is_valid_key function."""
|
|
|
|
def test_valid_simple_key(self):
|
|
"""Test valid simple key."""
|
|
assert is_valid_key("attack-on-titan")
|
|
|
|
def test_valid_key_with_numbers(self):
|
|
"""Test valid key with numbers."""
|
|
assert is_valid_key("a-time-called-you-2023")
|
|
|
|
def test_valid_key_with_underscores(self):
|
|
"""Test valid key with underscores."""
|
|
assert is_valid_key("a_time_called_you_2023")
|
|
|
|
def test_valid_key_starting_with_number(self):
|
|
"""Test valid key starting with number."""
|
|
assert is_valid_key("25-sai-no-joshikousei-2018")
|
|
|
|
def test_invalid_empty_key(self):
|
|
"""Test invalid empty key."""
|
|
assert not is_valid_key("")
|
|
|
|
def test_invalid_key_with_spaces(self):
|
|
"""Test invalid key with spaces."""
|
|
assert not is_valid_key("attack on titan")
|
|
|
|
def test_invalid_key_with_special_chars(self):
|
|
"""Test invalid key with special characters."""
|
|
assert not is_valid_key("attack@titan")
|
|
|
|
def test_invalid_key_with_unicode(self):
|
|
"""Test invalid key with unstripped unicode."""
|
|
assert not is_valid_key("attack\u00a0titan") # Non-breaking space
|
|
|
|
def test_invalid_single_char(self):
|
|
"""Test invalid single character key."""
|
|
assert not is_valid_key("a")
|
|
|
|
def test_valid_two_char_key(self):
|
|
"""Test valid two character key."""
|
|
assert is_valid_key("ab")
|
|
|
|
def test_invalid_key_starting_with_hyphen(self):
|
|
"""Test invalid key starting with hyphen."""
|
|
assert not is_valid_key("-attack")
|
|
|
|
|
|
class TestSanitizeKeyForUrl:
|
|
"""Test sanitize_key_for_url function."""
|
|
|
|
def test_standard_key_unchanged(self):
|
|
"""Test standard key remains unchanged."""
|
|
result = sanitize_key_for_url("attack-on-titan-2013")
|
|
assert result == "attack-on-titan-2013"
|
|
|
|
def test_spaces_replaced(self):
|
|
"""Test spaces are replaced with hyphens."""
|
|
result = sanitize_key_for_url("attack on titan")
|
|
assert result == "attack-on-titan"
|
|
|
|
def test_uppercase_preserved(self):
|
|
"""Test uppercase is preserved (use normalize_key for lowercase)."""
|
|
result = sanitize_key_for_url("AttackOnTitan")
|
|
# sanitize_key_for_url preserves case, only removes special chars
|
|
assert result == "AttackOnTitan"
|
|
|
|
def test_special_chars_removed(self):
|
|
"""Test special characters are removed."""
|
|
result = sanitize_key_for_url("Attack@#@On!Titan")
|
|
assert result == "AttackOnTitan"
|
|
|
|
def test_accents_preserved(self):
|
|
"""Test accented characters are preserved (use normalize_key for full normalization)."""
|
|
result = sanitize_key_for_url("AttäckÖnTïtan")
|
|
# Only removes truly problematic chars, preserves accented letters
|
|
assert "AttäckÖnTïtan" in result
|
|
|
|
def test_multiple_hyphens_collapses(self):
|
|
"""Test multiple hyphens are collapsed."""
|
|
result = sanitize_key_for_url("attack---on---titan")
|
|
assert result == "attack-on-titan"
|
|
|
|
def test_leading_trailing_hyphens_removed(self):
|
|
"""Test leading and trailing hyphens are removed."""
|
|
result = sanitize_key_for_url("-attack-on-titan-")
|
|
assert result == "attack-on-titan"
|
|
|
|
|
|
class TestValidateKeyUniqueness:
|
|
"""Test validate_key_uniqueness function."""
|
|
|
|
def test_unique_key(self):
|
|
"""Test key that is unique."""
|
|
existing_keys = {"attack-on-titan", "one-piece", "naruto"}
|
|
is_valid, error = validate_key_uniqueness("new-series", existing_keys)
|
|
assert is_valid is True
|
|
assert error == ""
|
|
|
|
def test_duplicate_key(self):
|
|
"""Test key that already exists."""
|
|
existing_keys = {"attack-on-titan", "one-piece", "naruto"}
|
|
is_valid, error = validate_key_uniqueness("one-piece", existing_keys)
|
|
assert is_valid is False
|
|
assert "already in use" in error
|
|
|
|
def test_empty_existing_set(self):
|
|
"""Test with empty existing keys set."""
|
|
is_valid, error = validate_key_uniqueness("new-series", set())
|
|
assert is_valid is True
|
|
assert error == ""
|
|
|
|
def test_key_differs_only_by_case(self):
|
|
"""Test key that differs only by case is NOT flagged by utility (API layer handles case-insensitivity)."""
|
|
existing_keys = {"attack-on-titan"} # lowercase in set
|
|
is_valid, error = validate_key_uniqueness("Attack-on-Titan", existing_keys)
|
|
# Utility function does case-sensitive check; API layer handles case-insensitivity
|
|
assert is_valid is True
|
|
assert error == ""
|
|
|
|
def test_same_key_same_case(self):
|
|
"""Test same key in existing set is flagged."""
|
|
existing_keys = {"my-series"}
|
|
is_valid, error = validate_key_uniqueness("my-series", existing_keys)
|
|
assert is_valid is False |