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:
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user