soulsync/tests/test_video_wishlist_clear.py
BoulderBadgeDad 3fa7b48f4c video wishlist badge: count YouTube videos too (header + sidebar)
bug: the wishlist badge read /wishlist/counts 'total', which was movies+episodes
only — YouTube videos (kind='video') are counted by a separate method and were
left out. so a wishlist of only youtube videos showed NO number; it only lit up
once tv episodes were added.

fix: the /wishlist/counts endpoint now folds youtube_wishlist_counts().video into
the total (+ exposes video/channel counts). the badge already live-refreshes on
wishlist-changed events (which youtube add/remove fire), so it now updates for
youtube too. db methods unchanged (their byte-identical contract is intact).
regression tests: mixed + youtube-only.
2026-06-25 23:15:45 -07:00

132 lines
5.1 KiB
Python

"""Wishlist 'Clear all' — empties one tab (movies / TV / YouTube) in a single call.
The three tabs map to different rows in video_wishlist: movies=kind 'movie',
TV=kind 'episode', YouTube=kind 'video'. clear_wishlist(kind) removes exactly the
one tab's rows and leaves the others alone.
"""
from __future__ import annotations
import pytest
from database.video_database import VideoDatabase
@pytest.fixture()
def db(tmp_path):
return VideoDatabase(database_path=str(tmp_path / "video_library.db"))
def _seed(db):
db.add_movie_to_wishlist(1, "M1")
db.add_movie_to_wishlist(2, "M2")
db.add_episodes_to_wishlist(9, "Show", [
{"season_number": 1, "episode_number": 1},
{"season_number": 1, "episode_number": 2}])
db.add_videos_to_wishlist({"youtube_id": "ch1", "title": "Ch"},
[{"youtube_id": "v1", "title": "V1"},
{"youtube_id": "v2", "title": "V2"}])
def test_clear_each_tab_removes_only_its_own_rows(db):
_seed(db)
assert db.wishlist_counts()["movie"] == 2
assert db.wishlist_counts()["episode"] == 2
assert db.youtube_wishlist_counts()["video"] == 2
# clear movies → TV + YouTube untouched
assert db.clear_wishlist("movie") == 2
assert db.wishlist_counts()["movie"] == 0
assert db.wishlist_counts()["episode"] == 2
assert db.youtube_wishlist_counts()["video"] == 2
# clear TV → only episodes gone
assert db.clear_wishlist("show") == 2
assert db.wishlist_counts()["episode"] == 0
assert db.youtube_wishlist_counts()["video"] == 2
# clear YouTube
assert db.clear_wishlist("youtube") == 2
assert db.youtube_wishlist_counts()["video"] == 0
def test_clear_rejects_unknown_kind(db):
_seed(db)
assert db.clear_wishlist("nope") == 0
assert db.wishlist_counts()["movie"] == 2 # nothing removed
def test_clear_empty_tab_is_a_noop(db):
assert db.clear_wishlist("movie") == 0
def test_counts_endpoint_total_includes_youtube_videos(tmp_path):
# regression: the header/sidebar wishlist badge reads this 'total'. It used to be
# movies+episodes only, so a wishlist of only YouTube videos showed NO badge.
from flask import Flask
import api.video as videoapi
db = VideoDatabase(database_path=str(tmp_path / "video_library.db"))
_seed(db) # 2 movies, 2 episodes (1 show), 2 youtube videos
videoapi._video_db = db
app = Flask(__name__)
app.register_blueprint(videoapi.create_video_blueprint(), url_prefix="/api/video")
client = app.test_client()
try:
r = client.get("/api/video/wishlist/counts").get_json()
assert r["success"]
assert r["movie"] == 2 and r["episode"] == 2 and r["video"] == 2
assert r["total"] == 6 # movies + episodes + YouTube videos (was 4)
finally:
videoapi._video_db = None
def test_counts_endpoint_youtube_only_shows_a_total(tmp_path):
# the exact reported case: only YouTube videos wished → badge must still show a number
from flask import Flask
import api.video as videoapi
db = VideoDatabase(database_path=str(tmp_path / "video_library.db"))
db.add_videos_to_wishlist({"youtube_id": "UC1", "title": "Ch"},
[{"youtube_id": "v1", "title": "A"}, {"youtube_id": "v2", "title": "B"}])
videoapi._video_db = db
app = Flask(__name__)
app.register_blueprint(videoapi.create_video_blueprint(), url_prefix="/api/video")
client = app.test_client()
try:
r = client.get("/api/video/wishlist/counts").get_json()
assert r["total"] == 2 and r["video"] == 2 and r["movie"] == 0
finally:
videoapi._video_db = None
def test_clear_endpoint(tmp_path):
from flask import Flask
import api.video as videoapi
db = VideoDatabase(database_path=str(tmp_path / "video_library.db"))
_seed(db)
videoapi._video_db = db
app = Flask(__name__)
app.register_blueprint(videoapi.create_video_blueprint(), url_prefix="/api/video")
client = app.test_client()
try:
r = client.post("/api/video/wishlist/clear", json={"kind": "movie"}).get_json()
assert r["success"] and r["removed"] == 2 and r["counts"]["movie"] == 0
assert client.post("/api/video/wishlist/clear", json={"kind": "bad"}).status_code == 400
finally:
videoapi._video_db = None
# ── frontend wiring ─────────────────────────────────────────────────────────
from pathlib import Path
_ROOT = Path(__file__).resolve().parent.parent
_JS = (_ROOT / "webui" / "static" / "video" / "video-wishlist.js").read_text(encoding="utf-8")
_INDEX = (_ROOT / "webui" / "index.html").read_text(encoding="utf-8")
def test_clear_button_wired_for_all_tabs():
assert "data-vwsh-clear" in _INDEX
assert "function clearAll(" in _JS
assert "/api/video/wishlist/clear" in _JS
assert "kind: kind" in _JS # clears the active tab
# shown only when the active tab has items
assert "function updateClearBtn(" in _JS