Fix manual YouTube searches for video IDs that begin with a dash by escaping leading '-' before building yt-dlp ytsearch expressions. This preserves normal search terms and already escaped user input while preventing yt-dlp from treating the ID as search syntax. Add regression coverage for both YouTube download search and video search paths. Fixes #684.
87 lines
2.7 KiB
Python
87 lines
2.7 KiB
Python
"""Regression tests for YouTube searches whose query starts with ``-``.
|
|
|
|
YouTube video IDs can start with a dash. yt-dlp's ``ytsearchN:`` parser
|
|
interprets a leading dash as search syntax unless escaped, so manual
|
|
searches for those IDs used to fan out into unrelated results.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from types import SimpleNamespace
|
|
|
|
from core import youtube_client
|
|
from core.youtube_client import YouTubeClient
|
|
|
|
|
|
def test_escape_ytsearch_query_handles_leading_dash():
|
|
assert YouTubeClient._escape_ytsearch_query("-4WUHJRhvrM") == r"\-4WUHJRhvrM"
|
|
assert YouTubeClient._escape_ytsearch_query(r"\-4WUHJRhvrM") == r"\-4WUHJRhvrM"
|
|
assert YouTubeClient._escape_ytsearch_query("Yo-Yo Ma") == "Yo-Yo Ma"
|
|
|
|
|
|
def test_search_escapes_leading_dash_before_yt_dlp(monkeypatch):
|
|
captured = []
|
|
|
|
class _FakeYoutubeDL:
|
|
def __init__(self, opts):
|
|
self.opts = opts
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb):
|
|
return False
|
|
|
|
def extract_info(self, search_query, download=False):
|
|
captured.append(search_query)
|
|
return {"entries": [{"id": "-4WUHJRhvrM", "title": "Unaccompanied Cello"}]}
|
|
|
|
monkeypatch.setattr(youtube_client.yt_dlp, "YoutubeDL", _FakeYoutubeDL)
|
|
|
|
client = YouTubeClient.__new__(YouTubeClient)
|
|
monkeypatch.setattr(client, "_get_best_audio_format", lambda formats: None)
|
|
monkeypatch.setattr(
|
|
client,
|
|
"_youtube_to_track_result",
|
|
lambda entry, best_audio: SimpleNamespace(filename=entry["title"]),
|
|
)
|
|
|
|
tracks, albums = asyncio.run(client.search("-4WUHJRhvrM"))
|
|
|
|
assert captured == [r"ytsearch50:\-4WUHJRhvrM"]
|
|
assert len(tracks) == 1
|
|
assert albums == []
|
|
|
|
|
|
def test_search_videos_escapes_leading_dash_before_yt_dlp(monkeypatch):
|
|
captured = []
|
|
|
|
class _FakeYoutubeDL:
|
|
def __init__(self, opts):
|
|
self.opts = opts
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb):
|
|
return False
|
|
|
|
def extract_info(self, search_query, download=False):
|
|
captured.append(search_query)
|
|
return {
|
|
"entries": [{
|
|
"id": "-4WUHJRhvrM",
|
|
"title": "Unaccompanied Cello",
|
|
"duration": 152,
|
|
"uploader": "Yo-Yo Ma",
|
|
}]
|
|
}
|
|
|
|
monkeypatch.setattr(youtube_client.yt_dlp, "YoutubeDL", _FakeYoutubeDL)
|
|
client = YouTubeClient.__new__(YouTubeClient)
|
|
|
|
results = asyncio.run(client.search_videos("-4WUHJRhvrM", max_results=8))
|
|
|
|
assert captured == [r"ytsearch8:\-4WUHJRhvrM"]
|
|
assert [r.video_id for r in results] == ["-4WUHJRhvrM"]
|