The first checkpoint of an item and the final flush beside it both read no state and both inserted; the loser failed on uq_plex_user_item. Two first saves each made a Watch later list. Both are created by an ON CONFLICT DO NOTHING insert and then read; a partial unique index holds one Watch later per user, and the migration folds an existing second list into the first. Plex keeps one per-item state map (rollup.item_state_map).
69 lines
2.6 KiB
Python
69 lines
2.6 KiB
Python
"""Migration 0112's data step, DB-lane: a user who already has two Watch later lists keeps the oldest,
|
|
with the other's videos moved in after its own (one it already held is not added twice), and the
|
|
one-per-user index then refuses a second list.
|
|
|
|
The schema half is checked by test_migrations.py; a video lost on the way is invisible there.
|
|
"""
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
import pytest
|
|
from alembic import command
|
|
from alembic.config import Config
|
|
from sqlalchemy import text
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
from app.catalog.models import Channel, Video
|
|
from app.db import engine
|
|
|
|
|
|
def test_a_second_watch_later_is_folded_into_the_first(db, make_user):
|
|
user, other = make_user(), make_user()
|
|
db.add(Channel(id="UCm", title="M"))
|
|
now = datetime.now(timezone.utc)
|
|
for v in ("v1", "v2", "v3"):
|
|
db.add(Video(id=v, channel_id="UCm", published_at=now, duration_seconds=60))
|
|
db.commit()
|
|
cfg = Config("alembic.ini")
|
|
command.downgrade(cfg, "0111_audit_request_id")
|
|
try:
|
|
with engine.begin() as conn:
|
|
ids = conn.execute(
|
|
text(
|
|
"INSERT INTO playlists (user_id, name, kind, position) VALUES"
|
|
" (:u, 'Watch later', 'watch_later', 0), (:u, 'Watch later', 'watch_later', 1),"
|
|
" (:o, 'Watch later', 'watch_later', 0) RETURNING id"
|
|
),
|
|
{"u": user.id, "o": other.id},
|
|
).scalars()
|
|
first, second, theirs = list(ids)
|
|
conn.execute(
|
|
text(
|
|
"INSERT INTO playlist_items (playlist_id, video_id, position) VALUES"
|
|
" (:a, 'v1', 0), (:b, 'v2', 0), (:b, 'v1', 1), (:c, 'v3', 0)"
|
|
),
|
|
{"a": first, "b": second, "c": theirs},
|
|
)
|
|
finally:
|
|
command.upgrade(cfg, "head")
|
|
|
|
with engine.connect() as conn:
|
|
lists = conn.execute(
|
|
text("SELECT id, user_id FROM playlists WHERE kind = 'watch_later' ORDER BY id")
|
|
).all()
|
|
assert [tuple(r) for r in lists] == [(first, user.id), (theirs, other.id)]
|
|
items = conn.execute(
|
|
text(
|
|
"SELECT playlist_id, video_id, position FROM playlist_items ORDER BY playlist_id, position"
|
|
)
|
|
).all()
|
|
assert [tuple(r) for r in items] == [(first, "v1", 0), (first, "v2", 1), (theirs, "v3", 0)]
|
|
with pytest.raises(IntegrityError), engine.begin() as conn:
|
|
conn.execute(
|
|
text(
|
|
"INSERT INTO playlists (user_id, name, kind, position)"
|
|
" VALUES (:u, 'Watch later', 'watch_later', 2)"
|
|
),
|
|
{"u": user.id},
|
|
)
|