Ctrl K
ComunidadGuías…

backend/tests/test_credit_remover.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Tests for credit_remover — staff credit line detection and removal."""

import pytest

# ─── Heuristic 1: Role markers ────────────────────────────────────────────────


def test_role_marker_translator_detected():
    from credit_remover import _is_credit_line

    assert (
        _is_credit_line(
            "Translation by (Translator) John Smith", total_end_ms=120_000, event_start_ms=10_000
        )
        is True
    )


def test_role_marker_qc_detected():
    from credit_remover import _is_credit_line

    assert (
        _is_credit_line("Quality Check (QC)", total_end_ms=120_000, event_start_ms=10_000) is True
    )


def test_role_marker_op_theme_detected():
    from credit_remover import _is_credit_line

    assert _is_credit_line("OP Theme", total_end_ms=120_000, event_start_ms=10_000) is True


def test_role_marker_ed_theme_detected():
    from credit_remover import _is_credit_line

    assert _is_credit_line("ED Theme", total_end_ms=120_000, event_start_ms=10_000) is True


# ─── Heuristic 2: Prefix patterns ─────────────────────────────────────────────


def test_prefix_credits_colon_detected():
    from credit_remover import _is_credit_line

    assert (
        _is_credit_line("Credits: Some Names", total_end_ms=120_000, event_start_ms=10_000) is True
    )


def test_prefix_staff_colon_detected():
    from credit_remover import _is_credit_line

    assert _is_credit_line("Staff: John Doe", total_end_ms=120_000, event_start_ms=10_000) is True


def test_prefix_translation_colon_detected():
    from credit_remover import _is_credit_line

    assert (
        _is_credit_line("Translation: Jane Smith", total_end_ms=120_000, event_start_ms=10_000)
        is True
    )


# ─── Heuristic 3: Duration heuristic ──────────────────────────────────────────


def test_duration_credit_region_detected():
    from credit_remover import _is_credit_line

    # Event starts at 23:50 in a 24-minute file → within last 90 s
    assert _is_credit_line("John Smith", total_end_ms=1_440_000, event_start_ms=1_430_000) is True


def test_duration_guard_question_mark_not_removed():
    from credit_remover import _is_credit_line

    # Even in credits region, lines with ? are preserved (dialogue guard)
    assert (
        _is_credit_line("Are you okay?", total_end_ms=1_440_000, event_start_ms=1_430_000) is False
    )


def test_duration_guard_exclamation_not_removed():
    from credit_remover import _is_credit_line

    assert _is_credit_line("Watch out!", total_end_ms=1_440_000, event_start_ms=1_430_000) is False


def test_duration_outside_credits_region_ignored():
    from credit_remover import _is_credit_line

    # Event at 5 s in a 24-minute file → not in credits region, no other heuristic.
    # Use 3-word input so Heuristic 4 (exactly 2 title-case words) does NOT trigger.
    assert (
        _is_credit_line("The door creaked loudly", total_end_ms=1_440_000, event_start_ms=5_000)
        is False
    )


def test_duration_threshold_respected(monkeypatch):
    """Custom credit_threshold_sec changes the boundary."""
    import os

    monkeypatch.setenv("SUBLARR_CREDIT_THRESHOLD_SEC", "30")
    from config import reload_settings

    reload_settings()

    from credit_remover import _is_credit_line

    # 40 s before end → outside 30 s threshold → not caught by duration heuristic alone
    # (name "John Smith" — heuristic 4 would catch it; use a safely non-matching name)
    assert _is_credit_line("Hello there", total_end_ms=1_440_000, event_start_ms=1_400_000) is False


# ─── Heuristic 4: Isolated capitalized names ──────────────────────────────────


def test_isolated_name_two_titlecase_words_detected():
    from credit_remover import _is_credit_line

    assert _is_credit_line("John Smith", total_end_ms=120_000, event_start_ms=5_000) is True


def test_isolated_name_with_punctuation_not_detected():
    from credit_remover import _is_credit_line

    assert _is_credit_line("Hello World!", total_end_ms=120_000, event_start_ms=5_000) is False


def test_isolated_name_three_words_not_detected():
    from credit_remover import _is_credit_line

    # Three words = not exactly two title-case words → no match
    assert (
        _is_credit_line("John Michael Smith", total_end_ms=120_000, event_start_ms=5_000) is False
    )


# ─── Normal dialogue NOT removed ──────────────────────────────────────────────


def test_normal_dialogue_not_removed():
    from credit_remover import _is_credit_line

    assert (
        _is_credit_line("I'll be back tomorrow.", total_end_ms=120_000, event_start_ms=30_000)
        is False
    )


def test_ass_round_trip():
    """remove_credits_from_ass removes credit dialogue events, preserves normal lines."""
    from credit_remover import remove_credits_from_ass

    ass_content = (
        "[Script Info]\nScriptType: v4.00+\n\n"
        "[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, "
        "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, "
        "Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, "
        "MarginV, Encoding\n"
        "Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,"
        "0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1\n\n"
        "[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"
        "Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Hello, how are you?\n"
        "Dialogue: 0,0:00:04.00,0:00:06.00,Default,,0,0,0,,Credits: John Smith\n"
    )
    cleaned, removed = remove_credits_from_ass(ass_content)
    assert removed == 1
    assert "Hello, how are you?" in cleaned
    assert "Credits: John Smith" not in cleaned


def test_ssa_and_srt_round_trip():
    """remove_credits_from_srt removes credit blocks, preserves dialogue."""
    from credit_remover import remove_credits_from_srt

    srt_content = (
        "1\n00:00:01,000 --> 00:00:03,000\nHello, how are you?\n\n"
        "2\n00:00:04,000 --> 00:00:06,000\nCredits: John Smith\n\n"
        "3\n00:00:07,000 --> 00:00:09,000\nI am fine, thanks.\n"
    )
    cleaned, removed = remove_credits_from_srt(srt_content)
    assert removed == 1
    assert "Hello, how are you?" in cleaned
    assert "Credits: John Smith" not in cleaned
    assert "I am fine" in cleaned


def test_backup_preserves_original_content(tmp_path):
    """Backup file matches original before modification."""
    import os

    src = tmp_path / "test.srt"
    original = "1\n00:00:01,000 --> 00:00:03,000\nCredits: John\n"
    src.write_text(original, encoding="utf-8")

    from credit_remover import remove_credits_from_srt

    # Verify remove function works correctly (backup created in route, not here)
    cleaned, removed = remove_credits_from_srt(original)
    assert removed >= 1
    # Original content unchanged by the pure function
    assert src.read_text(encoding="utf-8") == original