"""The admin UI: rendering, auth gating and CSRF. The templates are f-strings over a dict, so a renamed key is a KeyError at render time rather than a type error at import time — which is exactly the kind of breakage that only shows up when somebody opens the page. These tests render every page for real. """ from __future__ import annotations import pytest from ytstream import videos from ytstream.web import auth, templates from conftest import add_channel, add_video @pytest.fixture() def channel_row(conn, settings, channel): """The dict shape server.py builds for the channel table.""" add_video(conn, channel["id"], "vid00000001", state=videos.MATERIALISED) return { "id": channel["id"], "title": channel["title"], "handle": channel["handle"], "channel_id": channel["channel_id"], "retention_days": None, "global_retention": 30, "last_polled_at": "2026-08-12T14:00:00+00:00", "last_poll_ok": 1, "consecutive_poll_failures": 0, "episodes": 1, "source": "youtube", "missing_syncs": 0, "missing_threshold": 3, "latest": "2026-08-12", } def test_login_page_renders(settings): html = templates.login_page().decode() assert "ytstream" in html assert "youtube-automate" not in html assert 'type="password"' in html def test_login_page_shows_an_error(): assert "Wrong" in templates.login_page("Wrong password").decode() def test_index_page_renders(channel_row): html = templates.index_page( channels=[channel_row], settings_values={"retention_days": "30"}, settings_errors={}, csrf="tok", add_error=None, queue_depth=0, ).decode() assert "clabretro" in html assert "ytstream" in html # The provenance column replaced the meaningless size column. assert "youtube" in html assert "Size" not in html def test_index_page_renders_with_no_channels(): html = templates.index_page( channels=[], settings_values={}, settings_errors={}, csrf="tok", add_error=None, queue_depth=0, ).decode() assert "0 channel(s)" in html def test_absence_badge_appears_before_the_channel_disappears(channel_row): """A channel counting towards removal must be visible while it still exists.""" channel_row["missing_syncs"] = 2 html = templates.index_page( channels=[channel_row], settings_values={}, settings_errors={}, csrf="tok", add_error=None, queue_depth=0, ).decode() assert "absent 2/3 syncs" in html def test_no_absence_badge_when_healthy(channel_row): html = templates.index_page( channels=[channel_row], settings_values={}, settings_errors={}, csrf="tok", add_error=None, queue_depth=0, ).decode() assert "absent" not in html def test_hostile_channel_title_is_escaped(channel_row): channel_row["title"] = '' html = templates.index_page( channels=[channel_row], settings_values={}, settings_errors={}, csrf="tok", add_error=None, queue_depth=0, ).decode() assert "