from __future__ import annotations import time from pathlib import Path from unittest.mock import patch import pytest from app.core.models import Job from app.core.registry import _jobs from app.pipeline.collect import sweep_old_jobs @pytest.fixture(autouse=True) def _isolate_registry(): _jobs.clear() yield _jobs.clear() def _mkdir(jobs_dir: Path, name: str) -> Path: d = jobs_dir / name d.mkdir(parents=True) (d / "marker").write_bytes(b"x") return d def test_skip_active_job_even_if_old(tmp_path: Path): """An active (non-terminal) job's directory must never be swept, even if its created_at predates the TTL cutoff.""" d = _mkdir(tmp_path, "abcdefabcdef") job = Job(id="abcdefabcdef") job.status = "separating" job.created_at = time.time() - 999_999 # ancient _jobs[job.id] = job with patch("app.pipeline.collect.JOB_TTL_SECONDS", 60): sweep_old_jobs(tmp_path) assert d.is_dir() assert job.id in _jobs def test_sweeps_terminal_old_job(tmp_path: Path): d = _mkdir(tmp_path, "abcdefabcdee") job = Job(id="abcdefabcdee") job.status = "done" job.created_at = time.time() - 999_999 _jobs[job.id] = job with patch("app.pipeline.collect.JOB_TTL_SECONDS", 60): sweep_old_jobs(tmp_path) assert not d.exists() assert job.id not in _jobs def test_sweep_disabled_under_desktop(monkeypatch): """The desktop shell (STEMDECK_DESKTOP=1) opts out of the TTL sweep so a user's curated library isn't purged; the server/Docker default keeps it.""" from app.main import _sweep_disabled monkeypatch.delenv("STEMDECK_PERSIST_LIBRARY", raising=False) monkeypatch.setenv("STEMDECK_DESKTOP", "1") assert _sweep_disabled() is True monkeypatch.delenv("STEMDECK_DESKTOP", raising=False) assert _sweep_disabled() is False def test_sweep_disabled_under_persistent_library(monkeypatch): """A self-hosted server (run.sh) opts into a persistent library (STEMDECK_PERSIST_LIBRARY=1) so its processed tracks aren't purged.""" from app.main import _sweep_disabled monkeypatch.delenv("STEMDECK_DESKTOP", raising=False) monkeypatch.setenv("STEMDECK_PERSIST_LIBRARY", "1") assert _sweep_disabled() is True monkeypatch.delenv("STEMDECK_PERSIST_LIBRARY", raising=False) assert _sweep_disabled() is False @pytest.mark.asyncio async def test_sweep_loop_returns_immediately_under_desktop(monkeypatch): """In desktop mode the loop returns at once instead of entering the hourly cycle (wait_for would time out if it looped).""" import asyncio from app.main import _sweep_loop monkeypatch.setenv("STEMDECK_DESKTOP", "1") await asyncio.wait_for(_sweep_loop(), timeout=2) def test_keeps_recent_terminal_job(tmp_path: Path): d = _mkdir(tmp_path, "abcdefabcded") job = Job(id="abcdefabcded") job.status = "done" job.created_at = time.time() # fresh _jobs[job.id] = job with patch("app.pipeline.collect.JOB_TTL_SECONDS", 60): sweep_old_jobs(tmp_path) assert d.is_dir() assert job.id in _jobs def test_orphan_dir_falls_back_to_mtime(tmp_path: Path): """Directories with no registry entry (e.g. left over from a prior server run) still get swept by mtime.""" d = _mkdir(tmp_path, "abcdefabcdec") # Backdate the directory. old = time.time() - 999_999 import os os.utime(d, (old, old)) with patch("app.pipeline.collect.JOB_TTL_SECONDS", 60): sweep_old_jobs(tmp_path) assert not d.exists()