Ctrl K
ComunidadGuías…

backend/tests/test_audio_tracks.py

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

Archivo original ↗Preguntar a KiBot
"""Tests for `list_audio_tracks` service + GET /api/v1/audio/tracks route.

Plan B8 Task 1 — exposes the audio-track picker that the WaveformEditor
uses to switch between language tracks (Center for Dialog vs.
international audio).
"""

from __future__ import annotations

import os
from unittest.mock import patch

# ─── Service: list_audio_tracks ────────────────────────────────────────────

SAMPLE_PROBE = {
    "streams": [
        {
            "index": 0,
            "codec_type": "video",
            "codec_name": "h264",
            "tags": {},
            "disposition": {},
        },
        {
            "index": 1,
            "codec_type": "audio",
            "codec_name": "aac",
            "channels": 2,
            "tags": {"language": "jpn", "title": "Stereo"},
            "disposition": {"default": 1, "forced": 0},
        },
        {
            "index": 2,
            "codec_type": "audio",
            "codec_name": "ac3",
            "channels": 6,
            "tags": {"language": "eng", "title": "5.1 Surround"},
            "disposition": {"default": 0, "forced": 0},
        },
        {
            "index": 3,
            "codec_type": "subtitle",
            "codec_name": "ass",
            "tags": {"language": "jpn"},
            "disposition": {},
        },
    ]
}


class TestListAudioTracks:
    def test_returns_only_audio_streams(self):
        """Video and subtitle streams are filtered out."""
        from services.audio_visualizer import list_audio_tracks

        with patch("services.audio_visualizer.get_media_streams", return_value=SAMPLE_PROBE):
            tracks = list_audio_tracks("/media/ep.mkv")

        assert len(tracks) == 2
        assert all(t["codec_type"] == "audio" for t in tracks)

    def test_normalizes_track_shape(self):
        """Each track exposes index, codec, channels, language, title, default, forced."""
        from services.audio_visualizer import list_audio_tracks

        with patch("services.audio_visualizer.get_media_streams", return_value=SAMPLE_PROBE):
            tracks = list_audio_tracks("/media/ep.mkv")

        first = tracks[0]
        assert first["index"] == 1
        assert first["codec"] == "aac"
        assert first["channels"] == 2
        assert first["language"] == "jpn"
        assert first["title"] == "Stereo"
        assert first["default"] is True
        assert first["forced"] is False

    def test_handles_missing_tags_and_disposition(self):
        """Streams without tags/disposition return empty strings + False flags."""
        from services.audio_visualizer import list_audio_tracks

        sparse = {
            "streams": [
                {"index": 0, "codec_type": "audio", "codec_name": "opus", "channels": 1},
            ]
        }
        with patch("services.audio_visualizer.get_media_streams", return_value=sparse):
            tracks = list_audio_tracks("/media/ep.mkv")

        assert len(tracks) == 1
        track = tracks[0]
        assert track["language"] == ""
        assert track["title"] == ""
        assert track["default"] is False
        assert track["forced"] is False
        assert track["channels"] == 1

    def test_empty_probe_returns_empty_list(self):
        from services.audio_visualizer import list_audio_tracks

        with patch("services.audio_visualizer.get_media_streams", return_value={"streams": []}):
            tracks = list_audio_tracks("/media/ep.mkv")

        assert tracks == []


# ─── Route: GET /api/v1/audio/tracks ───────────────────────────────────────


def _media_root() -> str:
    return os.environ.get("SUBLARR_MEDIA_PATH", os.path.realpath("/tmp"))


class TestAudioTracksRoute:
    def test_requires_file_path_param(self, client):
        resp = client.get("/api/v1/audio/tracks")
        assert resp.status_code == 400
        assert "file_path" in resp.get_json()["error"]

    def test_rejects_path_traversal(self, client):
        resp = client.get("/api/v1/audio/tracks?file_path=/etc/passwd")
        assert resp.status_code == 403

    def test_returns_404_for_missing_file(self, client, tmp_path):
        # Path inside media_root but the file does not exist
        fake = tmp_path / "missing.mkv"
        resp = client.get(f"/api/v1/audio/tracks?file_path={fake}")
        assert resp.status_code == 404

    def test_returns_tracks_for_existing_file(self, client, tmp_path):
        # Create an empty file inside the media root and mock the probe
        video = tmp_path / "ep.mkv"
        video.write_bytes(b"\x00")  # ffprobe will be mocked, content ignored

        with patch("services.audio_visualizer.get_media_streams", return_value=SAMPLE_PROBE):
            resp = client.get(f"/api/v1/audio/tracks?file_path={video}")

        assert resp.status_code == 200
        body = resp.get_json()
        assert "tracks" in body
        assert len(body["tracks"]) == 2
        assert body["video_path"] == str(video)