"""Materialising: .strm contents, NFO sidecars, naming, and idempotency."""
from __future__ import annotations
from datetime import date
import pytest
from ytstream import config, strm, videos
from conftest import add_video
@pytest.fixture()
def video(conn, channel):
return add_video(conn, channel["id"], "dQw4w9WgXcQ",
title="A Video: With/Punctuation",
upload_date="2026-08-12", duration=1337)
@pytest.fixture()
def no_thumbs(monkeypatch):
"""Thumbnails are a network fetch; every test here runs without one."""
monkeypatch.setattr(strm, "fetch_thumbnail", lambda *a, **k: False)
def test_strm_contains_only_the_proxy_url(conn, settings, media_root, channel,
video, no_thumbs):
result = strm.materialise(conn, settings, channel, video)
path = media_root / result["rel_path"]
assert path.read_text() == "http://127.0.0.1:8099/watch/dQw4w9WgXcQ"
# No trailing newline, and nothing else in the file.
assert path.read_bytes() == b"http://127.0.0.1:8099/watch/dQw4w9WgXcQ"
def test_strm_url_follows_the_configured_base(conn, settings, media_root, channel,
video, no_thumbs):
settings.set("proxy_base_url", "http://127.0.0.1:9999/")
result = strm.materialise(conn, settings, channel, video)
assert (media_root / result["rel_path"]).read_text().startswith(
"http://127.0.0.1:9999/watch/"
)
def test_layout_matches_the_naming_scheme(conn, settings, media_root, channel,
video, no_thumbs):
result = strm.materialise(conn, settings, channel, video)
# Season = upload year, episode = MMDD*10 + ordinal.
assert result["season"] == 2026
assert result["episode"] == 8120
assert result["rel_path"] == (
"clabretro/Season 2026/"
"clabretro - S2026E8120 - A Video With Punctuation [dQw4w9WgXcQ].strm"
)
def test_nfo_is_written_alongside(conn, settings, media_root, channel, video,
no_thumbs):
result = strm.materialise(conn, settings, channel, video)
nfo_path = (media_root / result["rel_path"]).with_suffix(".nfo")
text = nfo_path.read_text()
assert "2026" in text
assert "8120" in text
assert "2026-08-12" in text
# durationinseconds is what stops a .strm episode showing a zero runtime
# before it has ever been played.
assert "1337" in text
assert 'type="youtube"' in text
def test_nfo_has_no_streamdetails(conn, settings, media_root, channel, video,
no_thumbs):
"""Pre-seeding them was measured to change nothing about Jellyfin probing."""
result = strm.materialise(conn, settings, channel, video)
text = (media_root / result["rel_path"]).with_suffix(".nfo").read_text()
assert "streamdetails" not in text
assert "fileinfo" not in text
def test_materialise_is_idempotent(conn, settings, media_root, channel, video,
no_thumbs):
first = strm.materialise(conn, settings, channel, video)
before = (media_root / first["rel_path"]).read_bytes()
second = strm.materialise(
conn, settings, channel, videos.get(conn, "dQw4w9WgXcQ")
)
assert second["rel_path"] == first["rel_path"]
assert (media_root / second["rel_path"]).read_bytes() == before
def test_row_is_marked_materialised(conn, settings, media_root, channel, video,
no_thumbs):
strm.materialise(conn, settings, channel, video)
row = videos.get(conn, "dQw4w9WgXcQ")
assert row["state"] == videos.MATERIALISED
assert row["rel_path"]
assert row["materialised_at"]
def test_episode_ordinals_increment_within_a_day(conn, settings, media_root,
channel, no_thumbs):
for index in range(3):
row = add_video(conn, channel["id"], f"vid{index:08d}",
upload_date="2026-08-12")
result = strm.materialise(conn, settings, channel, row)
assert result["episode"] == 8120 + index
def test_ordinals_are_stable_when_an_earlier_video_ages_out(
conn, settings, media_root, channel, no_thumbs
):
"""Aged-out rows keep their episode number, so later ordinals never shift."""
first = add_video(conn, channel["id"], "vid00000001", upload_date="2026-08-12")
strm.materialise(conn, settings, channel, first)
videos.mark_aged_out(conn, "vid00000001")
second = add_video(conn, channel["id"], "vid00000002", upload_date="2026-08-12")
result = strm.materialise(conn, settings, channel, second)
assert result["episode"] == 8121
def test_title_falls_back_to_the_video_id(conn, settings, media_root, channel,
no_thumbs):
"""Backfilled rows carry no title until the feed supplies one."""
row = add_video(conn, channel["id"], "vid00000001", title="",
upload_date="2026-08-12")
result = strm.materialise(conn, settings, channel, row)
assert "vid00000001" in result["rel_path"]
def test_write_show_creates_tvshow_nfo(media_root, channel):
strm.write_show(channel)
text = (media_root / "clabretro" / "tvshow.nfo").read_text()
assert "
clabretro" in text
assert channel["channel_id"] in text
def test_show_nfo_uses_the_channel_title_not_a_video_title(conn, media_root, channel):
"""The channel/video join has `title` on both sides, and reading the wrong one
renamed every series after whichever episode happened to be first."""
add_video(conn, channel["id"], "vid00000001", title="Some Episode Title")
strm.write_show(channel)
text = (media_root / "clabretro" / "tvshow.nfo").read_text()
assert "Some Episode Title" not in text
def test_remove_deletes_strm_and_sidecars(conn, settings, media_root, channel,
video, no_thumbs):
result = strm.materialise(conn, settings, channel, video)
strm_path = media_root / result["rel_path"]
stem = strm_path.name[: -len(".strm")]
thumb = strm_path.with_name(stem + "-thumb.jpg")
thumb.write_bytes(b"x" * 2000)
removed = strm.remove(videos.get(conn, "dQw4w9WgXcQ"))
assert removed == 3 # .strm, .nfo, -thumb.jpg
assert not strm_path.exists()
assert not thumb.exists()
def test_remove_leaves_files_it_does_not_own(conn, settings, media_root, channel,
video, no_thumbs):
result = strm.materialise(conn, settings, channel, video)
season_dir = (media_root / result["rel_path"]).parent
stranger = season_dir / "someone-elses-file.txt"
stranger.write_text("not ours")
strm.remove(videos.get(conn, "dQw4w9WgXcQ"))
assert stranger.exists()
def test_remove_channel_tree(conn, media_root, channel):
tree = media_root / "clabretro"
(tree / "Season 2026").mkdir(parents=True)
(tree / "tvshow.nfo").write_text("")
assert strm.remove_channel_tree(channel) is True
assert not tree.exists()
def test_remove_channel_tree_refuses_the_media_root(conn, media_root):
from conftest import add_channel
row = add_channel(conn, "UC" + "q" * 22, "Blank", "")
keep = media_root / "keep"
keep.mkdir()
assert strm.remove_channel_tree(row) is False
assert keep.exists()
def test_fetch_thumbnail_rejects_the_grey_placeholder(monkeypatch, tmp_path):
"""YouTube serves a tiny placeholder rather than a 404 for missing maxres."""
import urllib.request
class Response:
status = 200
def __init__(self, payload):
self.payload = payload
def read(self):
return self.payload
def __enter__(self):
return self
def __exit__(self, *exc):
return False
calls = []
def fake_open(request, timeout=None):
calls.append(request.full_url)
# maxres returns the placeholder; hq returns something real.
return Response(b"x" * 120 if "maxres" in request.full_url else b"y" * 5000)
monkeypatch.setattr(urllib.request, "urlopen", fake_open)
destination = tmp_path / "out-thumb.jpg"
assert strm.fetch_thumbnail("dQw4w9WgXcQ", destination) is True
assert destination.read_bytes() == b"y" * 5000
assert len(calls) == 2
def test_fetch_thumbnail_is_skipped_when_one_already_exists(monkeypatch, tmp_path):
import urllib.request
monkeypatch.setattr(urllib.request, "urlopen",
lambda *a, **k: pytest.fail("should not fetch"))
existing = tmp_path / "out-thumb.jpg"
existing.write_bytes(b"cached")
assert strm.fetch_thumbnail("dQw4w9WgXcQ", existing) is True
def test_untitled_video_does_not_get_its_id_written_back_as_a_title(
conn, settings, media_root, channel, no_thumbs
):
"""Writing the fallback back to the database makes the row look titled, which
permanently disables the title repair in discovery._record. That shipped once
and left five of twenty episodes named after their video ids."""
row = add_video(conn, channel["id"], "vid00000001", title="",
upload_date="2026-08-12")
result = strm.materialise(conn, settings, channel, row)
# The filename falls back to the id...
assert "vid00000001]" in result["rel_path"]
# ...but the row stays untitled, so a later feed poll can still repair it.
assert videos.get(conn, "vid00000001")["title"] == ""