- Durable Job Inbox: every async submit is journaled to the shared sqlite store and survives ComfyUI restarts; the Fal Job Inbox node lists pending/collected jobs and outputs the newest pending request_id/endpoint for one-wire recovery via Result by Request ID. - Provenance: Fal Save Media from URL writes a <file>.fal.json sidecar and embeds a fal_provenance PNG text chunk (preserving existing text chunks and ICC profiles), returning the request_id as a second output; new Fal Provenance from File node reads saved files back into endpoint_id + request_id for free re-materialization. - First web extension (web/ + /fal_api server routes): per-node cost pills rendered above every priced fal node (live estimates on free-typed endpoint fields), a fal sidebar tab with session spend, account balance and a job list with copy/cancel, and in-canvas endpoint search with price labels. All features degrade silently on older frontends; routes are exception-guarded and cached. Review fixes: PNG provenance embed preserves source text chunks and ICC profile; jobs route limit clamped. 15 new tests (91 total).
51 lines
1.4 KiB
Python
51 lines
1.4 KiB
Python
"""Unit tests for the /fal_api server routes' pure functions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
|
|
import pytest
|
|
from conftest import PKG, _load_package
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def routes():
|
|
_load_package()
|
|
return importlib.import_module(f"{PKG}.nodes.server_routes")
|
|
|
|
|
|
def test_import_without_comfy_server_is_safe(routes):
|
|
# loaded via conftest without ComfyUI's `server` module present
|
|
assert routes is not None
|
|
|
|
|
|
def test_pricing_map_covers_registry(routes):
|
|
pricing_map = routes._pricing_map()
|
|
assert len(pricing_map) > 100
|
|
sample = next(iter(pricing_map.values()))
|
|
assert "label" in sample
|
|
for key in pricing_map:
|
|
assert key.startswith("FalAPI_")
|
|
|
|
|
|
def test_search_models(routes):
|
|
results = routes._search_models(q="kling", category="", max_price=None, limit=10)
|
|
assert results
|
|
assert all("kling" in r["endpoint_id"].lower() or "kling" in r["title"].lower() for r in results)
|
|
|
|
|
|
def test_search_models_price_filter(routes):
|
|
unfiltered = routes._search_models(q="", category="", max_price=None, limit=100)
|
|
cheap = routes._search_models(q="", category="", max_price=0.02, limit=100)
|
|
assert len(cheap) < len(unfiltered)
|
|
|
|
|
|
def test_session_shape(routes):
|
|
payload = routes._session()
|
|
assert set(payload) >= {"total_usd", "calls"}
|
|
|
|
|
|
def test_jobs_degrades_gracefully(routes):
|
|
payload = routes._jobs(limit=5)
|
|
assert "jobs" in payload and "counts" in payload
|