test: the rewritten write handlers commit, and three more owner filters hold

Each handler that now calls a service and commits is driven through HTTP once.
A second account's watch state, Watch later list and private download profile
are never read or changed. The autotag job's orphan prune is seen committed.
This commit is contained in:
2026-10-20 00:45:22 +02:00
parent 3a5212cd8a
commit b04b2cbd62
3 changed files with 483 additions and 0 deletions
+113
View File
@@ -10,7 +10,10 @@ from datetime import datetime, timedelta, timezone
from sqlalchemy import func, select
from app import debugsession, notifications, quota
from app.catalog.models import Channel, Video
from app.downloads.models import DownloadJob, DownloadProfile
from app.models import DebugScreenshot, DebugSession, Notification, QuotaEvent
from app.reader.models import Playlist, PlaylistItem, VideoState
from app.routes import tokens as tokens_routes
from app.routes.auth import RESET_TTL, _issue_token
@@ -133,3 +136,113 @@ def test_screenshot_storage_counts_only_mine(db, make_user):
)
db.commit()
assert debugsession.stored_screenshot_bytes(db, me.id) == 10
# --- the units' owner filters, through their routes --------------------------------------------
# The same rule one layer up: a second account acting on the SAME video, list or profile id must
# reach only its own row. Each test holds the first account's row unchanged afterwards.
def _videos(db, *ids):
db.add(Channel(id="UCo", title="O"))
db.flush()
for vid in ids:
db.add(
Video(
id=vid,
channel_id="UCo",
published_at=datetime.now(timezone.utc),
duration_seconds=3600,
)
)
db.commit()
def _video_state(db, user, vid="v1"):
db.expire_all()
row = db.execute(
select(VideoState).where(VideoState.user_id == user.id, VideoState.video_id == vid)
).scalar_one_or_none()
return None if row is None else (row.status, row.position_seconds)
def test_unmarking_a_video_leaves_another_users_state(db, make_user, as_user):
me, other = make_user(), make_user()
_videos(db, "v1")
db.add(VideoState(user_id=other.id, video_id="v1", status="watched"))
db.commit()
r = as_user(me).post("/api/videos/v1/state", json={"status": "new"})
assert r.status_code == 200, r.text
assert _video_state(db, other) == ("watched", 0)
def test_clearing_a_video_leaves_another_users_state(db, make_user, as_user):
me, other = make_user(), make_user()
_videos(db, "v1")
db.add(VideoState(user_id=other.id, video_id="v1", status="hidden", position_seconds=90))
db.commit()
assert as_user(me).delete("/api/videos/v1/state").status_code == 200
assert _video_state(db, other) == ("hidden", 90)
def test_a_resume_point_is_set_and_cleared_on_my_own_row_only(db, make_user, as_user):
me, other = make_user(), make_user()
_videos(db, "v1")
db.add(VideoState(user_id=other.id, video_id="v1", status="hidden", position_seconds=90))
db.commit()
client = as_user(me)
body = {"position_seconds": 600, "duration_seconds": 3600}
assert client.post("/api/videos/v1/progress", json=body).status_code == 200
assert (_video_state(db, me), _video_state(db, other)) == (("new", 600), ("hidden", 90))
body = {"position_seconds": 0, "duration_seconds": 3600}
assert client.post("/api/videos/v1/progress", json=body).status_code == 200
assert (_video_state(db, me), _video_state(db, other)) == (None, ("hidden", 90))
def _watch_later_ids(db, user):
db.expire_all()
return sorted(
db.execute(
select(PlaylistItem.video_id)
.join(Playlist, Playlist.id == PlaylistItem.playlist_id)
.where(Playlist.user_id == user.id, Playlist.kind == "watch_later")
).scalars()
)
def test_watch_later_adds_and_removes_use_my_own_list(db, make_user, as_user):
me, other = make_user(), make_user()
_videos(db, "v1", "v2")
theirs = as_user(other).post("/api/playlists/watch-later", json={"video_id": "v1"}).json()
client = as_user(me)
mine = client.post("/api/playlists/watch-later", json={"video_id": "v2"}).json()
assert mine["playlist_id"] != theirs["playlist_id"]
# Their saved video is not on my list, so removing it from mine leaves theirs alone.
assert client.delete("/api/playlists/watch-later/v1").status_code == 200
assert (_watch_later_ids(db, me), _watch_later_ids(db, other)) == (["v2"], ["v1"])
def test_another_users_private_profile_is_refused_like_a_missing_one(db, make_user, as_user):
me, other = make_user(), make_user()
theirs = DownloadProfile(user_id=other.id, name="Theirs", spec={"format": "best"})
builtin = DownloadProfile(user_id=None, name="Best", spec={"format": "best"}, is_builtin=True)
db.add_all([theirs, builtin])
db.commit()
client = as_user(me)
def enqueue(profile_id):
return client.post(
"/api/downloads", json={"source": "dQw4w9WgXcQ", "profile_id": profile_id}
)
missing = enqueue(theirs.id + builtin.id + 1)
refused = enqueue(theirs.id)
assert (refused.status_code, refused.json()) == (404, missing.json())
assert refused.json()["detail"] == {"code": "not_found", "reason": "profile"}
db.expire_all()
assert db.execute(select(DownloadJob)).first() is None
# A built-in is everyone's to use.
r = enqueue(builtin.id)
assert r.status_code == 200, r.text
db.expire_all()
assert db.execute(select(DownloadJob.profile_id)).scalars().all() == [builtin.id]