"""Migration 0112 (milestone 430, #4428): retiring the sketch/doodle WIP title tier. Runs the migration's data step against real rows. The soft tags the operator stood behind (confirmed, or kept through the review strip) survive as `manual`; the rest go, and so do the unresolved review cards whose tag went with them. Hard title tags and their flags are untouched. """ import importlib.util from datetime import UTC, datetime from pathlib import Path import pytest from sqlalchemy import select from backend.app.models import PresentationReview, TagPositiveConfirmation from backend.app.models.tag import image_tag from backend.app.services.wip_title import resolve_wip_tag_id from tests.factories import make_image as _img pytestmark = pytest.mark.integration _MIGRATION = ( Path(__file__).resolve().parents[1] / "alembic" / "versions" / "0112_retire_soft_wip_title.py" ) def _retire(): spec = importlib.util.spec_from_file_location("m0112", _MIGRATION) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod.retire_soft_wip_tags def _source(db, image_id, tag_id): return db.execute( select(image_tag.c.source) .where(image_tag.c.image_record_id == image_id) .where(image_tag.c.tag_id == tag_id) ).scalar_one_or_none() def _review(db, image_id, tag_id): return db.execute( select(PresentationReview) .where(PresentationReview.image_record_id == image_id) .where(PresentationReview.tag_id == tag_id) ).scalar_one_or_none() def _flag(db, image_id, tag_id, *, resolved): db.add(PresentationReview( image_record_id=image_id, tag_id=tag_id, conflict_score=0.8, mode="process", resolved_at=datetime.now(UTC) if resolved else None, )) def test_retire_soft_wip_keeps_human_judged_and_clears_the_rest(db_sync): wip = resolve_wip_tag_id(db_sync) plain = _img(db_sync, "a" * 64) # soft, never looked at → removed confirmed = _img(db_sync, "b" * 64) # soft + confirmed → kept as manual kept = _img(db_sync, "c" * 64) # soft + "Keep tag" in the strip → manual pending = _img(db_sync, "d" * 64) # soft + open card → tag and card removed hard = _img(db_sync, "e" * 64) # hard title tag + open card → untouched for img in (plain, confirmed, kept, pending): db_sync.execute(image_tag.insert().values( image_record_id=img.id, tag_id=wip, source="wip_title_soft")) db_sync.execute(image_tag.insert().values( image_record_id=hard.id, tag_id=wip, source="wip_title")) db_sync.add(TagPositiveConfirmation(image_record_id=confirmed.id, tag_id=wip)) _flag(db_sync, kept.id, wip, resolved=True) _flag(db_sync, pending.id, wip, resolved=False) _flag(db_sync, hard.id, wip, resolved=False) db_sync.flush() _retire()(db_sync.connection()) db_sync.expire_all() assert _source(db_sync, plain.id, wip) is None assert _source(db_sync, confirmed.id, wip) == "manual" assert _source(db_sync, kept.id, wip) == "manual" assert _source(db_sync, pending.id, wip) is None assert _source(db_sync, hard.id, wip) == "wip_title" assert _review(db_sync, pending.id, wip) is None assert _review(db_sync, kept.id, wip) is not None # resolved history stays assert _review(db_sync, hard.id, wip) is not None # its tag is still on assert db_sync.execute( select(image_tag.c.image_record_id).where(image_tag.c.source == "wip_title_soft") ).first() is None