110 lines
4.0 KiB
Python
110 lines
4.0 KiB
Python
"""Deep scan re-queues already-completed ImportTasks.
|
|
|
|
Operator-flagged 2026-05-25: deep scan used to skip everything that
|
|
already had a non-failed ImportTask row, making a deep re-scan a no-op
|
|
when no new files were added. That defeated the entire point of deep
|
|
scan (re-apply sidecar metadata to existing rows). The skip-set now
|
|
splits by mode — quick keeps the old "any non-failed" semantics; deep
|
|
skips ONLY actively-in-flight statuses.
|
|
"""
|
|
|
|
import pytest
|
|
from PIL import Image
|
|
from sqlalchemy import func, select
|
|
|
|
from backend.app.models import ImportBatch, ImportSettings, ImportTask
|
|
from backend.app.tasks.scan import scan_directory
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
def _img(path):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
Image.new("RGB", (40, 40), (10, 200, 80)).save(path, "JPEG")
|
|
|
|
|
|
def test_deep_scan_requeues_completed_task(db_sync, tmp_path, monkeypatch):
|
|
"""Quick scan then deep scan of the same /import: the file completed
|
|
in the first run should be re-enqueued by the deep run (different
|
|
ImportTask id, same source_path)."""
|
|
import_root = tmp_path / "import"
|
|
settings = db_sync.execute(
|
|
select(ImportSettings).where(ImportSettings.id == 1)
|
|
).scalar_one()
|
|
settings.import_scan_path = str(import_root)
|
|
db_sync.commit()
|
|
|
|
src = import_root / "Mae" / "p.jpg"
|
|
_img(src)
|
|
|
|
from backend.app import celery_app
|
|
|
|
celery_app.celery.conf.task_always_eager = False # explicit
|
|
try:
|
|
first_batch_id = scan_directory.run(triggered_by="manual", mode="quick")
|
|
first_task = db_sync.execute(
|
|
select(ImportTask).where(ImportTask.batch_id == first_batch_id)
|
|
).scalar_one()
|
|
# Simulate the worker having finished it.
|
|
first_task.status = "complete"
|
|
db_sync.commit()
|
|
|
|
# Now deep scan: the SAME file should get a NEW ImportTask row.
|
|
second_batch_id = scan_directory.run(triggered_by="manual", mode="deep")
|
|
assert second_batch_id != first_batch_id
|
|
|
|
new_tasks_in_second_batch = db_sync.execute(
|
|
select(func.count())
|
|
.select_from(ImportTask)
|
|
.where(ImportTask.batch_id == second_batch_id)
|
|
).scalar_one()
|
|
assert new_tasks_in_second_batch == 1, (
|
|
"deep scan did not re-queue the completed file"
|
|
)
|
|
|
|
# And the second batch's task should be for the same source_path
|
|
# as the first (proves it's a re-queue, not a different file).
|
|
sp = db_sync.execute(
|
|
select(ImportTask.source_path)
|
|
.where(ImportTask.batch_id == second_batch_id)
|
|
).scalar_one()
|
|
assert sp == str(src)
|
|
finally:
|
|
celery_app.celery.conf.task_always_eager = False
|
|
|
|
|
|
def test_quick_scan_does_not_requeue_completed_task(db_sync, tmp_path):
|
|
"""The flip side: quick scan still keeps the old skip semantics —
|
|
a file with a completed ImportTask row from a prior batch is NOT
|
|
re-enqueued on a fresh quick scan."""
|
|
import_root = tmp_path / "import"
|
|
settings = db_sync.execute(
|
|
select(ImportSettings).where(ImportSettings.id == 1)
|
|
).scalar_one()
|
|
settings.import_scan_path = str(import_root)
|
|
db_sync.commit()
|
|
|
|
src = import_root / "Mae" / "q.jpg"
|
|
_img(src)
|
|
|
|
first_batch_id = scan_directory.run(triggered_by="manual", mode="quick")
|
|
first_task = db_sync.execute(
|
|
select(ImportTask).where(ImportTask.batch_id == first_batch_id)
|
|
).scalar_one()
|
|
first_task.status = "complete"
|
|
db_sync.commit()
|
|
|
|
second_batch_id = scan_directory.run(triggered_by="manual", mode="quick")
|
|
second_batch_count = db_sync.execute(
|
|
select(func.count())
|
|
.select_from(ImportTask)
|
|
.where(ImportTask.batch_id == second_batch_id)
|
|
).scalar_one()
|
|
assert second_batch_count == 0, (
|
|
"quick scan re-queued an already-completed task — should have skipped"
|
|
)
|
|
|
|
# And the second batch should self-finalize (files_seen=0).
|
|
second_batch = db_sync.get(ImportBatch, second_batch_id)
|
|
assert second_batch.status == "complete"
|