CI and images / lint (push) Successful in 3s
CI and images / extension-version (push) Successful in 3s
CI and images / extension-test (push) Successful in 18s
CI and images / frontend-build (push) Successful in 22s
CI and images / backend-lint-and-test (push) Successful in 32s
CI and images / integration (push) Successful in 2m22s
CI and images / sign-extension (push) Successful in 3s
CI and images / build-agent (push) Successful in 5s
CI and images / build-web (push) Successful in 1m39s
CI and images / smoke-web (push) Successful in 54s
CI and images / promote (push) Successful in 1s
The attachment path looked the post's source up by (artist, platform), taking the artist's lowest-id source. A Discord artist has one source per channel, so every archive or pdf from a later channel became an undated second post on the first channel's source. _post_for_sidecar now takes the downloading source, as upsert_post_record and _apply_sidecar already did. Migration 0114 re-points each shell's attachments to its dated twin and deletes the shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
107 lines
3.4 KiB
Python
107 lines
3.4 KiB
Python
"""Migration 0114 (#4435): the undated shell post a misfiled attachment made on
|
|
the artist's first Discord source is folded into the real, dated post."""
|
|
import importlib.util
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from sqlalchemy import select
|
|
|
|
from backend.app.models import (
|
|
Artist,
|
|
ImageProvenance,
|
|
Post,
|
|
PostAttachment,
|
|
Source,
|
|
)
|
|
from tests.factories import make_image as _img
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
_MIGRATION = (
|
|
Path(__file__).resolve().parents[1]
|
|
/ "alembic" / "versions" / "0114_fold_misfiled_attachment_posts.py"
|
|
)
|
|
|
|
|
|
def _fold():
|
|
spec = importlib.util.spec_from_file_location("m0114", _MIGRATION)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod.fold_misfiled_attachment_posts
|
|
|
|
|
|
def _source(db, artist, channel):
|
|
s = Source(
|
|
artist_id=artist.id, platform="discord",
|
|
url=f"https://discord.com/channels/1/{channel}",
|
|
)
|
|
db.add(s)
|
|
db.flush()
|
|
return s
|
|
|
|
|
|
def _post(db, artist, source, epid, when=None):
|
|
p = Post(
|
|
artist_id=artist.id, source_id=source.id, external_post_id=epid,
|
|
post_date=when,
|
|
post_url=f"https://discord.com/channels/1/x/{epid}" if when else None,
|
|
)
|
|
db.add(p)
|
|
db.flush()
|
|
return p
|
|
|
|
|
|
def _attach(db, post, sha, name):
|
|
db.add(PostAttachment(
|
|
post_id=post.id, artist_id=post.artist_id, sha256=sha,
|
|
path=f"/att/{sha}", original_filename=name, ext=".rar", size_bytes=1,
|
|
))
|
|
db.flush()
|
|
|
|
|
|
def test_shells_fold_into_their_dated_twin_and_nothing_else_moves(db_sync):
|
|
sent = datetime(2024, 12, 26, 1, 42, tzinfo=UTC)
|
|
artist = Artist(name="Yellow", slug="yellow")
|
|
db_sync.add(artist)
|
|
db_sync.flush()
|
|
first = _source(db_sync, artist, 100)
|
|
second = _source(db_sync, artist, 200)
|
|
|
|
# The #4435 shape: shell on the first source, real post on the second.
|
|
shell = _post(db_sync, artist, first, "555")
|
|
real = _post(db_sync, artist, second, "555", sent)
|
|
_attach(db_sync, shell, "a" * 64, "pack.rar")
|
|
# A shell whose attachment the real post already has: dropped, not doubled.
|
|
shell2 = _post(db_sync, artist, first, "556")
|
|
real2 = _post(db_sync, artist, second, "556", sent)
|
|
_attach(db_sync, shell2, "b" * 64, "same.rar")
|
|
_attach(db_sync, real2, "b" * 64, "same.rar")
|
|
# Left alone: no dated twin, and an undated post that holds an image.
|
|
lonely = _post(db_sync, artist, first, "557")
|
|
_attach(db_sync, lonely, "c" * 64, "lonely.rar")
|
|
with_image = _post(db_sync, artist, first, "558")
|
|
_post(db_sync, artist, second, "558", sent)
|
|
img = _img(db_sync, "d" * 64)
|
|
db_sync.add(ImageProvenance(image_record_id=img.id, post_id=with_image.id))
|
|
db_sync.flush()
|
|
shell_id, shell2_id = shell.id, shell2.id
|
|
|
|
folded = _fold()(db_sync.connection())
|
|
db_sync.expire_all()
|
|
|
|
assert folded == 2
|
|
assert db_sync.get(Post, shell_id) is None
|
|
assert db_sync.get(Post, shell2_id) is None
|
|
owners = dict(db_sync.execute(
|
|
select(PostAttachment.original_filename, PostAttachment.post_id)
|
|
).all())
|
|
assert owners["pack.rar"] == real.id
|
|
assert owners["lonely.rar"] == lonely.id
|
|
kept = db_sync.execute(
|
|
select(PostAttachment.id).where(PostAttachment.post_id == real2.id)
|
|
).scalars().all()
|
|
assert len(kept) == 1
|
|
assert db_sync.get(Post, lonely.id) is not None
|
|
assert db_sync.get(Post, with_image.id) is not None
|