Ctrl K
ComunidadGuías…

backend/tests/test_language_normalization_fixes.py

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

Archivo original ↗Preguntar a KiBot
"""Unit tests for language-code normalisation on destructive paths.

Audit follow-up to issue #159: several keep-language checks compared raw
container/filename tags (3-letter "ger"/"eng") against 2-letter keep lists
without canonicalisation, so target-language tracks and sidecars were
classified as foreign/disposable.
"""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest


class TestGetLanguageTags:
    def test_three_letter_input_expands_like_two_letter(self):
        from config_language_data import _get_language_tags

        assert {"de", "deu", "ger"} <= _get_language_tags("ger")
        assert _get_language_tags("ger") == _get_language_tags("de")

    def test_script_variant_includes_base_language_tags(self):
        from config_language_data import _get_language_tags

        tags = _get_language_tags("zh-hans")
        assert {"zh-hans", "zh", "zho", "chi"} <= tags

    def test_unknown_code_returns_itself(self):
        from config_language_data import _get_language_tags

        assert _get_language_tags("zz") == {"zz"}


class TestLanguageTableGaps:
    def test_bokmal_and_nynorsk_normalize_to_norwegian(self):
        from config_language_data import normalize_language_code

        assert normalize_language_code("nob") == "no"
        assert normalize_language_code("nb") == "no"
        assert normalize_language_code("nno") == "no"

    def test_bazarr_brazilian_portuguese_normalizes_to_pt(self):
        from config_language_data import normalize_language_code

        assert normalize_language_code("pob") == "pt"
        assert normalize_language_code("pt-br") == "pt"


class TestComputeKeepLangs:
    def _settings(self, auto_translate=False, source_language=""):
        s = MagicMock()
        s.wanted_auto_translate = auto_translate
        s.source_language = source_language
        return s

    def test_script_variant_target_keeps_base_code(self):
        from services.embedded_extractor import compute_keep_langs

        keep = compute_keep_langs({"target_languages": ["zh-hans"]}, self._settings())
        assert "zh-hans" in keep
        assert "zh" in keep, "generic chi/zho/zh tags must never be trashed for zh-hans"

    def test_und_only_targets_produce_empty_keep_set(self):
        from services.embedded_extractor import compute_keep_langs

        keep = compute_keep_langs({"target_languages": ["und", "und1", "und2"]}, self._settings())
        assert keep == set(), "an und-only keep set must trigger the empty-set no-op guards"

    def test_source_language_added_when_auto_translate(self):
        from services.embedded_extractor import compute_keep_langs

        keep = compute_keep_langs(
            {"target_languages": ["de"]},
            self._settings(auto_translate=True, source_language="eng"),
        )
        assert keep == {"de", "en"}


class TestPickPrimary:
    def test_three_letter_track_matches_two_letter_target(self):
        from services.embedded_extractor import _pick_primary

        extracted = [
            {"language": "jpn", "format": "ass", "sub_index": 0, "output_path": "/a.jpn.ass"},
            {"language": "ger", "format": "srt", "sub_index": 1, "output_path": "/a.ger.srt"},
        ]
        path, fmt, lang = _pick_primary(extracted, "de")
        assert lang == "ger"
        assert path == "/a.ger.srt"

    def test_no_prefix_false_positive_estonian_for_spanish(self):
        from services.embedded_extractor import _pick_primary

        extracted = [
            {"language": "est", "format": "srt", "sub_index": 0, "output_path": "/a.est.srt"},
            {"language": "jpn", "format": "ass", "sub_index": 1, "output_path": "/a.jpn.ass"},
        ]
        path, fmt, lang = _pick_primary(extracted, "es")
        # No Spanish track exists — fall back to the first entry, but never
        # "match" Estonian just because it shares the prefix.
        assert lang == "est"  # fallback extracted[0], not a target match
        # The regression: with prefix matching, "est" was treated as a target
        # match for "es". Verify a real Spanish track wins when present.
        extracted.append(
            {"language": "spa", "format": "srt", "sub_index": 2, "output_path": "/a.spa.srt"}
        )
        path, fmt, lang = _pick_primary(extracted, "es")
        assert lang == "spa"


class TestRemoveForeignSubtitleStreamsDefense:
    def test_bare_two_letter_target_spares_three_letter_tagged_stream(self, tmp_path):
        """Even when a caller (wrongly) passes bare 2-letter codes, the
        target-language stream must survive."""
        import remux as remux_mod

        video = tmp_path / "ep.mkv"
        video.write_bytes(b"fake")

        probe = {
            "streams": [
                {
                    "codec_type": "subtitle",
                    "index": 2,
                    "tags": {"language": "ger"},
                },
                {
                    "codec_type": "subtitle",
                    "index": 3,
                    "tags": {"language": "jpn"},
                },
            ]
        }

        with (
            patch("remux.get_media_streams", return_value=probe),
            patch("remux.remove_subtitle_streams", return_value="/bak") as mock_remove,
        ):
            remux_mod.remove_foreign_subtitle_streams(
                video_path=str(video),
                target_languages={"de"},
            )

        streams = mock_remove.call_args.kwargs["streams"]
        assert streams == [(3, 1)], "ger must not be foreign for target {'de'}"


class TestResolveCombineSources:
    def test_three_letter_sidecar_tags_match_profile_codes(self, tmp_path):
        from services.combine_service import resolve_combine_sources

        sidecars = [
            {"language": "ger", "format": "ass", "path": str(tmp_path / "ep.ger.ass")},
            {"language": "eng", "format": "ass", "path": str(tmp_path / "ep.eng.ass")},
        ]

        with patch("services.sidecar_scan.scan_subtitle_sidecars", return_value=sidecars):
            present, missing = resolve_combine_sources(str(tmp_path / "ep.mkv"), ["de", "en"])

        assert missing == []
        assert [p["language"] for p in present] == ["de", "en"]
        assert present[0]["path"].endswith("ep.ger.ass")