* refactor(nodes): remove superseded LTX Director extra nodes + harden registry (#198) Remove the two experimental nodes layered on the WhatDreamsCost fork — KoolookAudioTranscriptTimeline (k_audio_timeline.py) and KoolookTimelineEditor (k_timeline_editor.py) — plus the transcribe_audio_timeline.py CLI and its test. The audio-transcript to Director-timing job now lives inside the fork via the Director's audio_transcript_json input, so the standalone nodes are redundant. Also drops the orphaned [audio]/faster-whisper extra and updates packaging, dev-sync, web, and docs references. Harden the node registry against the #183 failure: __init__.py now imports each node group independently and installs the /koolook/presets/* routes regardless of node-import results, so one missing or broken module degrades to 'that node absent' instead of zeroing every node and 404'ing the sidebar disk routes. A new test keeps every root k_*.py listed in dev-sync's RUNTIME_PATHS. Closes #198. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(nodes): ship loader gates via dev-sync-audio; generalize RUNTIME_PATHS guard (#198) Address the K-Flame review on #199: MEDIUM — the #198 refactor made koolook_versioning.py a single registration gate, but dev-sync-audio shipped the new __init__.py without it (and the k_*.py-glob guard test couldn't see it). Add koolook_versioning.py AND koolook_install_guard.py (whose absolute-import fallback raises uncaught) to AUDIO_PATHS so a scoped sync can never leave the loader pointing at a module the target lacks. Generalize the guard test: parse __init__.py's actual load-time import targets (relative imports + _merge_node_group calls) and assert each is in RUNTIME_PATHS, instead of globbing k_*.py which missed the non-k_ gates. Add an AUDIO_PATHS gate-coverage test. LOW — CHANGELOG now records the [audio]/faster-whisper + transcribe_audio_timeline.py removal as an external-contract break. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
3.1 KiB
Python
100 lines
3.1 KiB
Python
"""Safety coverage for scripts/sync_to_dev_audio.py."""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
_SCRIPT = Path(__file__).resolve().parents[2] / "scripts" / "sync_to_dev_audio.py"
|
|
_spec = importlib.util.spec_from_file_location("sync_to_dev_audio", _SCRIPT)
|
|
assert _spec is not None and _spec.loader is not None
|
|
sync_to_dev_audio = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(sync_to_dev_audio)
|
|
|
|
|
|
def _stale_path(root: Path) -> Path:
|
|
return root / "web" / "whatdreamscost_koolook_v1_3_2"
|
|
|
|
|
|
def test_remove_stale_paths_removes_directory(tmp_path: Path) -> None:
|
|
stale = _stale_path(tmp_path)
|
|
stale.mkdir(parents=True)
|
|
(stale / "ltx_director.js").write_text("old extension\n", encoding="utf-8")
|
|
|
|
removed = sync_to_dev_audio.remove_stale_paths(
|
|
tmp_path,
|
|
dry_run=False,
|
|
verbose=False,
|
|
)
|
|
|
|
assert removed == 1
|
|
assert not stale.exists()
|
|
|
|
|
|
def test_remove_stale_paths_dry_run_preserves_directory(tmp_path: Path) -> None:
|
|
stale = _stale_path(tmp_path)
|
|
stale.mkdir(parents=True)
|
|
|
|
removed = sync_to_dev_audio.remove_stale_paths(
|
|
tmp_path,
|
|
dry_run=True,
|
|
verbose=False,
|
|
)
|
|
|
|
assert removed == 1
|
|
assert stale.exists()
|
|
|
|
|
|
def test_remove_stale_paths_idempotent_when_absent(tmp_path: Path) -> None:
|
|
removed = sync_to_dev_audio.remove_stale_paths(
|
|
tmp_path,
|
|
dry_run=False,
|
|
verbose=False,
|
|
)
|
|
|
|
assert removed == 0
|
|
|
|
|
|
def test_remove_stale_paths_unlinks_symlink_without_touching_target(tmp_path: Path) -> None:
|
|
current = tmp_path / "web" / "whatdreamscost_koolook"
|
|
current.mkdir(parents=True)
|
|
keep = current / "ltx_director.js"
|
|
keep.write_text("current extension\n", encoding="utf-8")
|
|
stale = _stale_path(tmp_path)
|
|
try:
|
|
stale.symlink_to(current, target_is_directory=True)
|
|
except OSError as exc:
|
|
pytest.skip(f"symlink creation unavailable: {exc}")
|
|
|
|
removed = sync_to_dev_audio.remove_stale_paths(
|
|
tmp_path,
|
|
dry_run=False,
|
|
verbose=False,
|
|
)
|
|
|
|
assert removed == 1
|
|
assert not stale.exists()
|
|
assert keep.read_text(encoding="utf-8") == "current extension\n"
|
|
|
|
|
|
def test_target_is_repo_root_detects_source_repo() -> None:
|
|
assert sync_to_dev_audio.target_is_repo_root(sync_to_dev_audio._dev.REPO_ROOT)
|
|
|
|
|
|
def test_audio_paths_ship_init_load_gates() -> None:
|
|
"""dev-sync-audio ships ``__init__.py``, so it must also ship the modules
|
|
``__init__`` imports at load before any node group. Those gates are not
|
|
per-group-guarded: a missing ``koolook_install_guard`` makes the absolute
|
|
import fallback raise uncaught (plugin dead), and a missing
|
|
``koolook_versioning`` makes the context probe mislabel its ImportError as
|
|
a non-Comfy context and register nothing (#198 / #183).
|
|
"""
|
|
audio_paths = set(sync_to_dev_audio.AUDIO_PATHS)
|
|
required = {"__init__.py", "koolook_install_guard.py", "koolook_versioning.py"}
|
|
missing = sorted(required - audio_paths)
|
|
assert not missing, (
|
|
f"dev-sync-audio ships __init__.py but not its load-time gate(s): {missing}"
|
|
)
|