Files
mcma-backend/tests/test_transcode_service.py
Цвылев Александр Вадимович fb7827d09c test: cover lyrics, transcode, and recommendation
DB-free unit tests for the three previously-untested features:
- lyrics: get-or-fetch caching, not_found TTL, force refetch, graceful miss;
  plus the LRC -> structured-lyrics serializer (timing, plain fallback, empty)
- transcode: path helpers, segment-name traversal guard, cache hit/miss, the
  unknown/not-downloaded 404 paths
- reco: ML path (order-preserving batched hydration) + metadata fallback for
  similar/radio, exclude handling, reason codes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 11:07:22 +03:00

158 lines
5.1 KiB
Python

"""Transcode cache-path helpers + request-side service (§6.6) — DB-free.
Covers the pure path math, the segment-name traversal guard, cache
hit/miss lookups, and the "not yet downloaded" / unknown-track 404 paths.
"""
import datetime as dt
import uuid
from pathlib import Path
import pytest
from app.application.transcode_service import (
QUALITY_BITRATE,
TranscodeService,
bitrate_for_quality,
hls_playlist_path,
hls_segment_path,
opus_path,
remove_track_cache,
track_cache_dir,
)
from app.domain.entities import Track
from app.domain.errors import NotFoundError
def _track(*, storage_uri: str | None = "tracks/aa/song.mp3") -> Track:
now = dt.datetime.now(dt.UTC)
return Track(
id=uuid.uuid4(),
title="Song",
artist_id=uuid.uuid4(),
album_id=None,
storage_uri=storage_uri,
file_format="mp3",
file_size=1,
source="upload",
source_id="x",
duration_seconds=None,
genre=None,
year=None,
track_number=None,
metadata_status="pending",
metadata_error=None,
enriched_at=None,
availability="local",
created_at=now,
updated_at=now,
)
class FakeTrackRepo:
def __init__(self, track: Track | None) -> None:
self._track = track
async def get_by_id(self, track_id: uuid.UUID) -> Track | None:
return self._track
# -- pure helpers -------------------------------------------------------------
def test_bitrate_for_quality_known_and_unknown() -> None:
assert bitrate_for_quality("high") == QUALITY_BITRATE["high"]
assert bitrate_for_quality("medium") == 96
assert bitrate_for_quality("low") == 64
# "original" and anything unrecognised mean "serve the master, no transcode".
assert bitrate_for_quality("original") is None
assert bitrate_for_quality("nonsense") is None
def test_path_helpers_are_under_the_track_dir(tmp_path: Path) -> None:
tid = uuid.uuid4()
cache = track_cache_dir(tmp_path, tid)
assert cache == tmp_path / str(tid)
assert opus_path(tmp_path, tid, 128) == cache / "opus_128.opus"
assert hls_playlist_path(tmp_path, tid) == cache / "hls" / "playlist.m3u8"
@pytest.mark.parametrize(
"name",
["seg_000.ts", "seg_1234.ts"],
)
def test_hls_segment_path_accepts_valid_names(tmp_path: Path, name: str) -> None:
tid = uuid.uuid4()
resolved = hls_segment_path(tmp_path, tid, name)
assert resolved == track_cache_dir(tmp_path, tid) / "hls" / name
@pytest.mark.parametrize(
"name",
["../../etc/passwd", "seg_.ts", "seg_00.tsx", "playlist.m3u8", "seg_00.ts", "..", ""],
)
def test_hls_segment_path_rejects_traversal_and_junk(tmp_path: Path, name: str) -> None:
assert hls_segment_path(tmp_path, uuid.uuid4(), name) is None
def test_remove_track_cache_is_best_effort(tmp_path: Path) -> None:
tid = uuid.uuid4()
d = track_cache_dir(tmp_path, tid)
(d / "hls").mkdir(parents=True)
(d / "opus_128.opus").write_bytes(b"x")
remove_track_cache(tmp_path, tid)
assert not d.exists()
# Idempotent: deleting an already-absent cache doesn't raise.
remove_track_cache(tmp_path, tid)
# -- request-side service -----------------------------------------------------
async def test_resolve_quality_file_hit_and_miss(tmp_path: Path) -> None:
track = _track()
svc = TranscodeService(tracks=FakeTrackRepo(track), cache_root=tmp_path)
# Miss: nothing on disk yet.
assert await svc.resolve_quality_file(track.id, "high") is None
# "original" never has a rendition.
assert await svc.resolve_quality_file(track.id, "original") is None
# Hit: once the rendition exists it's returned.
path = opus_path(tmp_path, track.id, 128)
path.parent.mkdir(parents=True)
path.write_bytes(b"opus")
assert await svc.resolve_quality_file(track.id, "high") == path
async def test_hls_playlist_requires_downloaded_track(tmp_path: Path) -> None:
tid = uuid.uuid4()
# Unknown track → 404.
with pytest.raises(NotFoundError):
await TranscodeService(tracks=FakeTrackRepo(None), cache_root=tmp_path).hls_playlist(tid)
# Known but not downloaded (no storage_uri) → 404.
pending = _track(storage_uri=None)
with pytest.raises(NotFoundError):
await TranscodeService(tracks=FakeTrackRepo(pending), cache_root=tmp_path).hls_playlist(
pending.id
)
async def test_hls_playlist_miss_then_hit(tmp_path: Path) -> None:
track = _track()
svc = TranscodeService(tracks=FakeTrackRepo(track), cache_root=tmp_path)
assert await svc.hls_playlist(track.id) is None
playlist = hls_playlist_path(tmp_path, track.id)
playlist.parent.mkdir(parents=True)
playlist.write_text("#EXTM3U")
assert await svc.hls_playlist(track.id) == playlist
def test_hls_segment_returns_none_for_missing_file(tmp_path: Path) -> None:
svc = TranscodeService(tracks=FakeTrackRepo(_track()), cache_root=tmp_path)
# Valid name but no file on disk.
assert svc.hls_segment(uuid.uuid4(), "seg_000.ts") is None
# Junk name is refused outright.
assert svc.hls_segment(uuid.uuid4(), "../secret") is None