import xml.etree.ElementTree as ET from youtube_automate import nfo HOSTILE = ( "Ampersands & angle and \"quotes\"\n" "control chars: \x00\x07\x1b\n" "emoji 🎬 and em-dash — and links https://example.com/?a=1&b=2" ) class TestEpisodeNfo: def build(self, **overrides): kwargs = dict( title="Video Title", show_title="Some Channel", season=2026, episode=8110, plot="A plot.", aired="2026-08-11", duration_seconds=762, video_id="dQw4w9WgXcQ", ) kwargs.update(overrides) return nfo.episode_nfo(**kwargs) def test_is_well_formed_xml(self): root = ET.fromstring(self.build()) assert root.tag == "episodedetails" def test_hostile_description_still_parses(self): root = ET.fromstring(self.build(plot=HOSTILE)) plot = root.findtext("plot") assert "&" in plot and "" in plot assert "🎬" in plot def test_control_characters_are_stripped(self): plot = ET.fromstring(self.build(plot=HOSTILE)).findtext("plot") for bad in ("\x00", "\x07", "\x1b"): assert bad not in plot def test_newlines_are_preserved(self): plot = ET.fromstring(self.build(plot="one\ntwo")).findtext("plot") assert plot == "one\ntwo" def test_runtime_is_rounded_minutes(self): assert ET.fromstring(self.build(duration_seconds=762)).findtext("runtime") == "13" def test_short_video_still_gets_at_least_one_minute(self): assert ET.fromstring(self.build(duration_seconds=20)).findtext("runtime") == "1" def test_runtime_omitted_when_duration_unknown(self): assert ET.fromstring(self.build(duration_seconds=None)).find("runtime") is None def test_unique_id_marks_youtube_as_default(self): unique = ET.fromstring(self.build()).find("uniqueid") assert unique.get("type") == "youtube" assert unique.get("default") == "true" assert unique.text == "dQw4w9WgXcQ" def test_season_and_episode_are_present(self): root = ET.fromstring(self.build()) assert root.findtext("season") == "2026" assert root.findtext("episode") == "8110" def test_title_keeps_characters_that_the_filename_strips(self): root = ET.fromstring(self.build(title="Hermitcraft S11#11: Expanding Business")) assert root.findtext("title") == "Hermitcraft S11#11: Expanding Business" def test_empty_plot_does_not_break(self): assert ET.fromstring(self.build(plot=None)).find("plot") is not None class TestTvshowNfo: def test_well_formed_and_carries_channel_id(self): root = ET.fromstring(nfo.tvshow_nfo("clabretro", HOSTILE, "UCabc123")) assert root.tag == "tvshow" assert root.findtext("title") == "clabretro" assert root.findtext("studio") == "YouTube" assert root.find("uniqueid").text == "UCabc123" class TestWrite: def test_write_is_atomic_and_leaves_no_temp_file(self, tmp_path): target = tmp_path / "sub" / "tvshow.nfo" nfo.write(target, b"") assert target.read_bytes() == b"" assert list(tmp_path.rglob("*.tmp")) == [] def test_overwrites_existing(self, tmp_path): target = tmp_path / "tvshow.nfo" nfo.write(target, b"") nfo.write(target, b"") assert target.read_bytes() == b""