fix(reconcile): preserve from_attachment_id when merging duplicate posts (#73/#87)
Milestone #73 (reconcile duplicate gallery-dl/native post rows) shipped in eff6427; closing it out after today's #87 work, which added a seam it didn't account for. _repoint_post_links drops a loser post's ImageProvenance row on the (image, post) uniqueness collision — and that row may now carry from_attachment_id (which archive the file was extracted from). For the exact gallery-dl->native case this targets, the keeper is the native stub (no archive) and the loser is the gallery-dl row that extracted the member, so a blind delete silently lost the containing-archive linkage. Carry from_attachment_id onto the keeper's surviving row (when NULL) before dropping the collision. The rarer PostAttachment-collision case (both dup posts captured the same archive blob) doesn't arise in the targeted scenario — the archive lives only on the gallery-dl post, so it re-points straight to the keeper and the FK stays valid. Test: collision merge preserves the loser's from_attachment_id on the keeper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -853,3 +853,36 @@ def test_reconcile_dedups_provenance_collision(db_sync, tmp_path):
|
||||
select(ImageProvenance.post_id).where(ImageProvenance.image_record_id == img_id)
|
||||
).scalars().all()
|
||||
assert prov == [native_id]
|
||||
|
||||
|
||||
def test_reconcile_preserves_from_attachment_on_provenance_collision(db_sync, tmp_path):
|
||||
# The gallery-dl loser row recorded which archive the image came out of
|
||||
# (#87); the native keeper row didn't. Merging drops the loser provenance row
|
||||
# on the (image, post) collision — but must first carry its from_attachment_id
|
||||
# onto the keeper so the containing-archive linkage survives.
|
||||
a = _make_artist(db_sync, slug="rc4")
|
||||
img = _make_image(db_sync, artist=a, path=str(tmp_path / "z.jpg"), sha256="e" * 64)
|
||||
legacy = Post(artist_id=a.id, external_post_id="9", raw_metadata={"post_id": 77})
|
||||
native = Post(artist_id=a.id, external_post_id="77", raw_metadata={"post_id": 77})
|
||||
db_sync.add_all([legacy, native])
|
||||
db_sync.flush()
|
||||
att = PostAttachment(
|
||||
post_id=legacy.id, artist_id=a.id, sha256="f" * 64,
|
||||
path=str(tmp_path / "bundle.cbz"), original_filename="bundle.cbz",
|
||||
ext=".cbz", size_bytes=9,
|
||||
)
|
||||
db_sync.add(att)
|
||||
db_sync.flush()
|
||||
db_sync.add(ImageProvenance(
|
||||
image_record_id=img.id, post_id=legacy.id, from_attachment_id=att.id,
|
||||
))
|
||||
db_sync.add(ImageProvenance(image_record_id=img.id, post_id=native.id))
|
||||
img_id, native_id, att_id = img.id, native.id, att.id
|
||||
db_sync.commit()
|
||||
|
||||
cleanup_service.reconcile_duplicate_posts(db_sync, dry_run=False)
|
||||
rows = db_sync.execute(
|
||||
select(ImageProvenance.post_id, ImageProvenance.from_attachment_id)
|
||||
.where(ImageProvenance.image_record_id == img_id)
|
||||
).all()
|
||||
assert rows == [(native_id, att_id)]
|
||||
|
||||
Reference in New Issue
Block a user