fix(cleanup): live prune uses the same predicate as the preview (data loss)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 34s
CI / integration (push) Failing after 3m10s

The fandom/chapter exclusions added in fb05c5e only touched find_unused_tags
(the preview SAMPLE). prune_unused_tags re-implemented the predicate inline for
the dry-run COUNT and the live DELETE with only the image_tag + series_page
checks — so the preview showed a safe list of names while the delete removed
every fandom (and chaptered series). Operator-flagged 2026-06-08: real data loss
— assigned fandoms deleted, their characters SET-NULLed.

Extract _unused_tag_conditions() as the single source of truth and use it for
the preview, the count, AND the delete, so they can never diverge again. Added a
prune-commit test asserting the LIVE delete spares a character's fandom and a
chaptered series.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 18:04:02 -04:00
parent 408fcd488a
commit de4ef6ae74
2 changed files with 61 additions and 39 deletions
+24
View File
@@ -362,6 +362,30 @@ def test_prune_unused_tags_commit_deletes_them(db_sync, tmp_path):
assert "bye" not in surviving_names
def test_prune_unused_tags_commit_spares_fandom_and_chaptered_series(db_sync):
# The LIVE delete (not just the preview) must use the same predicate — it
# previously had only the image_tag/series_page checks and deleted every
# fandom the preview correctly excluded (operator-flagged 2026-06-08, real
# data loss). Both dry-run count and the commit must spare referenced tags.
fandom = _make_tag(db_sync, name="Creux", kind=TagKind.fandom)
char = _make_tag(db_sync, name="OcChar", kind=TagKind.character)
char.fandom_id = fandom.id
series = _make_tag(db_sync, name="EmptySeries", kind=TagKind.series)
db_sync.add(SeriesChapter(series_tag_id=series.id, chapter_number=1))
_make_tag(db_sync, name="GenuinelyUnused")
db_sync.commit()
# dry-run count agrees with the delete: only the 1 truly-unused tag.
assert cleanup_service.prune_unused_tags(db_sync, dry_run=True)["count"] == 1
result = cleanup_service.prune_unused_tags(db_sync, dry_run=False)
assert result["deleted"] == 1
surviving = db_sync.execute(select(Tag.name)).scalars().all()
assert "Creux" in surviving # fandom referenced by a character
assert "EmptySeries" in surviving # series referenced by a chapter
assert "GenuinelyUnused" not in surviving
# --- reset_content_tagging ------------------------------------------