Files
MajoorWaldi-ComfyUI-Majoor-…/tests/features/test_filesystem_helpers.py
T
Ewald f5917eef6e feat(execution): implement idle grace period and enhance asset handling
- Add EXECUTION_IDLE_GRACE_MS config to control post-execution delay
- Implement post-execution work scheduling to optimize resource handling
- Enhance asset upsert logic for live assets during stack finalization
- Add runtime status synchronization to backend
- Improve execution start/end handling with new job ID tracking
- Refactor live asset event processing to reduce complexity
- Optimize imports and event handling in various modules
2026-04-09 01:55:32 +02:00

410 lines
13 KiB
Python

from collections import OrderedDict
from pathlib import Path
import pytest
from mjr_am_backend.routes.handlers import filesystem as fs
def test_normalize_sort_key_accepts_known_and_fallback() -> None:
assert fs._normalize_sort_key("name_asc") == "name_asc"
assert fs._normalize_sort_key(" MTIME_ASC ") == "mtime_asc"
assert fs._normalize_sort_key("bad") == "mtime_desc"
assert fs._normalize_sort_key(None) == "mtime_desc"
def test_safe_mtime_int_handles_common_inputs() -> None:
assert fs._safe_mtime_int(True) == 0
assert fs._safe_mtime_int(12) == 12
assert fs._safe_mtime_int(12.8) == 12
assert fs._safe_mtime_int("13") == 13
assert fs._safe_mtime_int("13.7") == 13
assert fs._safe_mtime_int("") == 0
assert fs._safe_mtime_int("x") == 0
def test_boolish_and_extension_normalizers() -> None:
assert fs._is_truthy_boolish(True)
assert fs._is_truthy_boolish(1)
assert fs._is_truthy_boolish("1")
assert not fs._is_truthy_boolish(False)
assert not fs._is_truthy_boolish("yes")
assert fs._normalize_extension(".PNG,") == "png"
assert fs._normalize_extension(" jpg ") == "jpg"
assert fs._normalize_extension(None) == ""
assert fs._normalize_extensions([".PNG", "png", "jpg", None]) == ["png", "jpg"]
def test_background_entry_peek_and_skip_logic(monkeypatch) -> None:
monkeypatch.setattr(fs, "_BACKGROUND_SCAN_LAST", OrderedDict())
fs._BACKGROUND_SCAN_LAST["a"] = {"last_mono": 100.0, "in_progress": True}
assert fs._peek_background_entry("a") == (100.0, True)
assert fs._should_skip_scan_enqueue("a", 200.0, 10.0)
fs._BACKGROUND_SCAN_LAST["b"] = 50.0
assert fs._peek_background_entry("b") == (50.0, False)
assert not fs._should_skip_scan_enqueue("b", 80.0, 10.0)
def test_enqueue_background_scan_job_caps_queue(monkeypatch) -> None:
monkeypatch.setattr(fs, "_SCAN_PENDING", OrderedDict())
monkeypatch.setattr(fs, "_SCAN_PENDING_MAX", 2)
assert fs._enqueue_background_scan_job("k1", {"v": 1})
assert fs._enqueue_background_scan_job("k2", {"v": 2})
assert not fs._enqueue_background_scan_job("k2", {"v": 22})
assert fs._enqueue_background_scan_job("k3", {"v": 3})
assert list(fs._SCAN_PENDING.keys()) == ["k2", "k3"]
def test_filesystem_entry_matching_predicates() -> None:
assert not fs._filesystem_entry_matches_filters(
filename="x.bin",
kind="unknown",
query_lower="x",
browse_all=False,
filter_kind="",
filter_extensions=[],
)
assert not fs._filesystem_entry_matches_filters(
filename="photo.png",
kind="image",
query_lower="abc",
browse_all=False,
filter_kind="",
filter_extensions=[],
)
assert not fs._filesystem_entry_matches_filters(
filename="photo.png",
kind="image",
query_lower="photo",
browse_all=False,
filter_kind="video",
filter_extensions=[],
)
assert fs._filesystem_entry_matches_filters(
filename="photo.png",
kind="image",
query_lower="photo",
browse_all=False,
filter_kind="image",
filter_extensions=["png"],
)
def test_parse_filters_and_fast_path_flag() -> None:
opts = fs._parse_filesystem_listing_filters(
"*",
{
"kind": "image",
"min_rating": 0,
"extensions": [".png"],
"mtime_start": None,
"mtime_end": None,
},
"none",
)
assert opts["browse_all"] is True
assert opts["sort_key"] == "none"
assert opts["filter_extensions"] == ["png"]
assert fs._can_use_listing_fast_path(opts)
opts2 = dict(opts)
opts2["filter_workflow_only"] = False
assert not fs._can_use_listing_fast_path(opts2)
opts3 = dict(opts)
opts3["filter_min_rating"] = 1
assert not fs._can_use_listing_fast_path(opts3)
def test_parse_filters_coerces_bool_and_mtime_safely() -> None:
opts = fs._parse_filesystem_listing_filters(
"*",
{
"has_workflow": "false",
"tags": ["Cat", "cat", ""],
"mtime_start": "bad",
"mtime_end": "123",
},
"mtime_desc",
)
assert opts["filter_workflow_only"] is False
assert opts["filter_tags"] == ["cat"]
assert opts["filter_mtime_start"] is None
assert opts["filter_mtime_end"] == 123
assert not fs._can_use_listing_fast_path(opts)
def test_prefilter_and_sort_entries() -> None:
all_entries = [
{"filename": "b.png", "kind": "image", "mtime": 10},
{"filename": "a.png", "kind": "image", "mtime": 20},
{"filename": "x.mp4", "kind": "video", "mtime": 5},
{"filename": "", "kind": "image", "mtime": 1},
"bad",
]
filtered = fs._prefilter_cached_filesystem_entries(
all_entries, filter_kind="image", browse_all=False, q_lower=".png"
)
assert [x["filename"] for x in filtered] == ["b.png", "a.png"]
fs._sort_filesystem_entries(filtered, "name_asc")
assert [x["filename"] for x in filtered] == ["a.png", "b.png"]
fs._sort_filesystem_entries(filtered, "mtime_desc")
assert [x["filename"] for x in filtered] == ["a.png", "b.png"]
def test_post_filters_helpers_and_pagination() -> None:
items = [
{
"filename": "a.png",
"kind": "image",
"ext": ".png",
"rating": 5,
"tags": ["cat", "blue"],
"has_workflow": 1,
"workflow_type": "T2I",
"mtime": 200,
},
{
"filename": "b.jpg",
"kind": "image",
"ext": ".jpg",
"rating": 1,
"tags": ["dog"],
"has_workflow": 0,
"workflow_type": "",
"mtime": 100,
},
]
assert fs._passes_extension_filter(items[0], ["png"])
assert not fs._passes_extension_filter(items[1], ["png"])
assert fs._passes_tag_filter(items[0], ["cat"])
assert not fs._passes_tag_filter(items[1], ["cat"])
assert fs._passes_kind_filter(items[0], "image")
assert fs._passes_rating_filter(items[0], 3)
assert not fs._passes_rating_filter(items[1], 3)
assert fs._passes_workflow_filter(items[0], True)
assert not fs._passes_workflow_filter(items[1], True)
assert fs._passes_workflow_filter(items[1], False)
assert fs._passes_mtime_window(items[0], 150, 300)
assert not fs._passes_mtime_window(items[1], 150, 300)
assert fs._passes_name_query(items[0], browse_all=False, q_lower="a.")
paged, total = fs._paginate_filesystem_listing_entries(
items,
filter_extensions=["png", "jpg"],
filter_tags=["cat"],
filter_kind="image",
filter_min_rating=1,
filter_workflow_only=None,
filter_workflow_type="",
filter_mtime_start=50,
filter_mtime_end=250,
browse_all=False,
q_lower=".",
limit=1,
offset=0,
)
assert total == 1
assert len(paged) == 1
assert paged[0]["filename"] == "a.png"
def test_listing_payload_and_args() -> None:
payload = fs._build_filesystem_listing_payload([], total=0, limit=10, offset=5, query="q", sort_key="none")
assert payload["sort"] == "none"
assert payload["offset"] == 5
args = fs._listing_args_from_opts(
{
"q": "*",
"q_lower": "*",
"browse_all": True,
"filter_kind": "",
"filter_min_rating": 0,
"filter_workflow_only": False,
"filter_extensions": ["png"],
"filter_tags": ["cat"],
"filter_mtime_start": None,
"filter_mtime_end": None,
"sort_key": "mtime_desc",
}
)
assert args["sort_key"] == "mtime_desc"
assert args["filter_extensions"] == ["png"]
assert args["filter_tags"] == ["cat"]
def test_lookup_and_enrichment_helpers() -> None:
class _Svc:
def lookup_assets_by_filepaths(self, _fps):
return None
lookup, fps = fs._resolve_filesystem_lookup(_Svc(), [{"filepath": "a"}, {"filepath": ""}])
assert callable(lookup)
assert fps == ["a"]
assert fs._extract_enrichment_mapping(None) is None
class _Res:
ok = True
data = {"a": {"id": 5, "rating": 2, "tags": ["x"], "has_workflow": 1, "has_generation_data": 0, "root_id": "r"}}
mapping = fs._extract_enrichment_mapping(_Res())
assert mapping is not None
asset = {"filepath": "a", "rating": 0, "tags": []}
fs._apply_db_enrichment_row(asset, mapping)
assert asset["id"] == 5
assert asset["root_id"] == "r"
def test_cache_entry_helpers(monkeypatch) -> None:
cached = {"dir_mtime_ns": 10, "watch_token": 3, "entries": []}
assert fs._cache_entry_matches_dir_state(cached, dir_mtime_ns=10, watch_token=3)
assert not fs._cache_entry_matches_dir_state(cached, dir_mtime_ns=9, watch_token=3)
monkeypatch.setattr(fs, "FS_LIST_CACHE_TTL_SECONDS", 10)
monkeypatch.setattr(fs.time, "monotonic", lambda: 200.0)
assert fs._cache_entry_is_fresh({"cached_at_mono": 195.0})
assert not fs._cache_entry_is_fresh({"cached_at_mono": 100.0})
def test_resolve_filesystem_listing_target_ok_and_invalid(tmp_path: Path) -> None:
root = tmp_path / "root"
sub = root / "sub"
sub.mkdir(parents=True)
ok = fs._resolve_filesystem_listing_target(root, "sub")
assert ok.ok
assert ok.data["target_dir_resolved"] == sub.resolve()
bad = fs._resolve_filesystem_listing_target(root, "../outside")
assert not bad.ok
assert bad.code == "INVALID_INPUT"
def test_collect_filesystem_entries_and_window(tmp_path: Path) -> None:
root = tmp_path / "root"
root.mkdir()
good = root / "a.png"
other = root / "b.jpg"
unknown = root / "c.zzz"
good.write_bytes(b"x")
other.write_bytes(b"y")
unknown.write_bytes(b"z")
entries = fs._collect_filesystem_entries(root, root, "input", "rid")
names = sorted([e["filename"] for e in entries])
assert names == ["a.png", "b.jpg"]
out, total = fs._collect_filesystem_entries_window(
root,
root,
"input",
"rid",
query_lower="a",
browse_all=False,
filter_kind="image",
filter_extensions=["png"],
offset=0,
limit=10,
)
assert total == 1
assert len(out) == 1
assert out[0]["filename"] == "a.png"
def test_filesystem_dir_cache_state_handles_watch_token(tmp_path: Path, monkeypatch) -> None:
base = tmp_path
target = tmp_path
called = {"watch": 0}
def _watch(_base: str) -> None:
called["watch"] += 1
monkeypatch.setattr(fs, "ensure_fs_list_cache_watching", _watch)
monkeypatch.setattr(fs, "get_fs_list_cache_token", lambda _b: 9)
monkeypatch.setattr(fs, "FS_LIST_CACHE_WATCHER_ENABLED", True)
res = fs._filesystem_dir_cache_state(base, target)
assert res.ok
assert res.data["watch_token"] == 9
assert called["watch"] == 1
@pytest.mark.asyncio
async def test_dispatch_filesystem_listing_path_selects_fast_and_cached(monkeypatch, tmp_path: Path) -> None:
async def _fast(*_args, **_kwargs):
return fs.Result.Ok({"path": "fast"})
async def _cached(*_args, **_kwargs):
return fs.Result.Ok({"path": "cached"})
monkeypatch.setattr(fs, "_list_filesystem_assets_fast_path", _fast)
monkeypatch.setattr(fs, "_list_filesystem_assets_cached_path", _cached)
base = tmp_path
target = tmp_path
listing_args_fast = {
"q": "*",
"q_lower": "*",
"browse_all": True,
"filter_kind": "",
"filter_min_rating": 0,
"filter_workflow_only": None,
"filter_workflow_type": "",
"filter_extensions": [],
"filter_tags": [],
"filter_mtime_start": None,
"filter_mtime_end": None,
"sort_key": "none",
"opts": {
"sort_key": "none",
"filter_min_rating": 0,
"filter_workflow_only": None,
"filter_tags": [],
"filter_mtime_start": None,
"filter_mtime_end": None,
},
}
r1 = await fs._dispatch_filesystem_listing_path(
base,
target,
"input",
None,
listing_args=listing_args_fast,
limit=10,
offset=0,
index_service=None,
)
assert r1.ok
assert r1.data["path"] == "fast"
listing_args_cached = dict(listing_args_fast)
listing_args_cached["opts"] = {"sort_key": "mtime_desc"}
r2 = await fs._dispatch_filesystem_listing_path(
base,
target,
"input",
None,
listing_args=listing_args_cached,
limit=10,
offset=0,
index_service=None,
)
assert r2.ok
assert r2.data["path"] == "cached"