"""FC-2d-vi: Importer(deep=True) re-derives on an already-imported row.""" import json import pytest from PIL import Image from sqlalchemy import func, select from backend.app.models import ( ImageProvenance, ImageRecord, ImportSettings, Post, ) from backend.app.services.importer import Importer from backend.app.services.thumbnailer import Thumbnailer pytestmark = pytest.mark.integration @pytest.fixture def import_layout(tmp_path): import_root = tmp_path / "import" images_root = tmp_path / "images" import_root.mkdir() images_root.mkdir() return import_root, images_root def _mk(db_sync, import_layout, *, deep): import_root, images_root = import_layout settings = db_sync.execute( select(ImportSettings).where(ImportSettings.id == 1) ).scalar_one() return Importer( session=db_sync, images_root=images_root, import_root=import_root, thumbnailer=Thumbnailer(images_root=images_root), settings=settings, deep=deep, ) def _img(path): path.parent.mkdir(parents=True, exist_ok=True) Image.new("RGB", (50, 50), (90, 30, 200)).save(path, "JPEG") def test_deep_rederives_phash_and_provenance(db_sync, import_layout): import_root, _ = import_layout src = import_root / "Mae" / "p.jpg" _img(src) # First import (quick). Images get a phash on import; null it + # primary_post_id to simulate a pre-FC-2d-i+ii/v record, then add a # sidecar to simulate provenance that didn't exist at first import. quick = _mk(db_sync, import_layout, deep=False) r1 = quick.import_one(src) assert r1.status == "imported" rec = db_sync.get(ImageRecord, r1.image_id) rec.phash = None rec.primary_post_id = None db_sync.commit() src.with_suffix(".jpg.json").write_text(json.dumps( {"category": "patreon", "id": "9", "title": "P"})) deep = _mk(db_sync, import_layout, deep=True) r2 = deep.import_one(src) # Outcome flipped from "skipped+duplicate_hash" to "refreshed" 2026-05-25 # so the UI can surface deep scan's actual work instead of showing it as # a no-op. See ImportResult.status comment + _deep_rederive docstring. assert r2.status == "refreshed" assert r2.image_id == rec.id assert r2.skip_reason is None assert r2.error is None db_sync.expire_all() rec2 = db_sync.get(ImageRecord, rec.id) assert rec2.phash is not None assert db_sync.execute( select(func.count()).select_from(ImageRecord) ).scalar_one() == 1 # no new record post = db_sync.execute(select(Post)).scalar_one() assert db_sync.execute( select(func.count()).select_from(ImageProvenance) .where(ImageProvenance.image_record_id == rec.id) ).scalar_one() == 1 assert rec2.primary_post_id == post.id # Idempotent: a second deep pass adds no duplicate provenance. _mk(db_sync, import_layout, deep=True).import_one(src) db_sync.expire_all() assert db_sync.execute( select(func.count()).select_from(ImageProvenance) ).scalar_one() == 1 def test_deep_false_is_plain_skip(db_sync, import_layout): import_root, _ = import_layout src = import_root / "Mae" / "q.jpg" _img(src) _mk(db_sync, import_layout, deep=False).import_one(src) r = _mk(db_sync, import_layout, deep=False).import_one(src) assert r.status == "skipped" assert r.error == "sha256 already present"