"""Finding files in the artists' folders that the library has no record of, and importing them where they lie (services.library_scan + the `library` batch).""" import io import os import time import pytest from PIL import Image from sqlalchemy import select import backend.app.tasks.import_file as import_file from backend.app.models import Artist, ImageRecord, ImportSettings, ImportTask from backend.app.services import library_scan from backend.app.services.importer import Importer from backend.app.services.thumbnailer import Thumbnailer pytestmark = pytest.mark.integration def _jpeg(seed: int) -> bytes: im = Image.new("RGB", (256, 256)) px = im.load() for y in range(256): for x in range(256): px[x, y] = ((x * seed) % 256, (y * (seed + 1)) % 256, (x ^ y) % 256) buf = io.BytesIO() im.save(buf, "JPEG") return buf.getvalue() def _write(path, data: bytes, *, age_days: float = 2): path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(data) t = time.time() - age_days * 86400 os.utime(path, (t, t)) return path @pytest.fixture def layout(db_sync, tmp_path): db_sync.add(Artist(name="Libscan Maewix", slug="libscan-maewix")) db_sync.commit() post = tmp_path / "Libscan-Maewix" / "patreon" / "2026-05-01_123_Pack" files = { "missing": _write(post / "01_art.jpg", _jpeg(1)), "owned": _write(post / "02_owned.jpg", _jpeg(2)), "sidecar": _write(post / "01_art.json", b'{"category": "patreon", "id": "123"}'), "staging": _write(post / "03_big.zip.part", b"partial"), "recent": _write(post / "04_new.jpg", _jpeg(4), age_days=0), "not_media": _write(post / "05_game.exe", b"MZ"), "no_artist": _write(tmp_path / "libscan-nobody" / "x" / "a.jpg", _jpeg(5)), "store": _write(tmp_path / "thumbs" / "abc" / "f.jpg", _jpeg(6)), } db_sync.add(ImageRecord( path=str(files["owned"]), sha256="libscan-owned".ljust(64, "x"), size_bytes=files["owned"].stat().st_size, mime="image/jpeg", width=256, height=256, origin="downloaded", integrity_status="ok", )) db_sync.commit() return tmp_path, files def test_finds_only_unrecorded_media_in_an_artists_folder(db_sync, layout): """A differently-cased folder still matches its artist. Recorded files, sidecars, staging files, the library's own stores, folders that match no artist, files changed in the last day and non-media are not listed.""" root, files = layout found, summary = library_scan.find_unimported(db_sync, root) assert [u.path for u in found] == [files["missing"]] assert found[0].artist_slug == "libscan-maewix" assert found[0].task_type == "media" assert summary["files"] == 1 assert summary["recent"] == 1 assert summary["not_media"] == 1 assert summary["no_artist"] == 1 assert summary["artists"][0]["slug"] == "libscan-maewix" assert summary["artists"][0]["samples"] == [ "Libscan-Maewix/patreon/2026-05-01_123_Pack/01_art.jpg", ] def test_queue_makes_a_library_batch_of_what_find_lists(db_sync, layout, monkeypatch): """The import queued is exactly the previewed list.""" root, files = layout queued = [] monkeypatch.setattr(import_file, "enqueue_import", lambda tid, tt: queued.append((tid, tt))) summary = library_scan.queue_unimported(db_sync, root) tasks = db_sync.execute( select(ImportTask).where(ImportTask.batch_id == summary["batch_id"]) ).scalars().all() assert [t.source_path for t in tasks] == [str(files["missing"])] assert tasks[0].batch.scan_mode == "library" assert queued == [(tasks[0].id, "media")] def test_a_library_import_attaches_in_place_and_removes_a_copy(db_sync, layout, monkeypatch): """The file stays where it is and becomes the record's file. A byte-identical copy found later is removed from disk, since the library already holds it.""" root, files = layout monkeypatch.setattr(import_file, "IMAGES_ROOT", root) importer = Importer( session=db_sync, images_root=root, import_root=root, thumbnailer=Thumbnailer(images_root=root), settings=ImportSettings.load_sync(db_sync), ) result = import_file._attach_where_it_lies(db_sync, importer, files["missing"]) assert result.status == "imported" record = db_sync.get(ImageRecord, result.image_id) assert record.path == str(files["missing"]) assert files["missing"].exists() assert db_sync.get(Artist, record.artist_id).slug == "libscan-maewix" copy = _write(files["missing"].parent / "09_copy.jpg", files["missing"].read_bytes()) again = import_file._attach_where_it_lies(db_sync, importer, copy) assert again.status == "skipped" assert not copy.exists() assert files["missing"].exists()