Files
DumiFlex 28487f1a68 feat(engine): database and tag list move into user/wildcard-pipeline/
Both files sat loose in ComfyUI's user directory next to ComfyUI's own. That
was fine with one of them and stopped being fine at two. One folder is also one
thing to back up and one thing to delete.

The subfolder alone would have read as data loss: every existing install has a
real library at the old path, and the extension would have come up empty while
the file sat one level up. So `engine/db/relocate.py` moves them in, once, on
first boot.

Four constraints shaped the mover:

Never overwrite. A file at the destination means either a finished migration
or something the user put there; both outrank anything we could infer, so the
legacy file is left alone and reported as skipped.

Run before the database is opened. SQLite keeps `-wal` and `-shm` sidecars
beside the main file and moving that set out from under an open connection
corrupts it. The call sits in `__init__.py` ABOVE the node imports, which can
reach the DB — placement is load-bearing, not stylistic.

Move the sidecars with it. A `-wal` holds committed transactions not yet
checkpointed into the main file, so moving the `.db` alone silently drops the
most recent writes.

Never raise. This runs at extension import; a throw would take the whole
extension down over housekeeping. Failures are collected into a report the
caller logs, and the degraded state is "keep using the old location".

`shutil.move` rather than `Path.rename` because a user directory that is a
mount or a symlink elsewhere would make rename fail with EXDEV.

`TAG_FILE_NAME` is duplicated in `relocate.py` rather than imported from
`wp_api/tags.py`: the directory contract keeps `engine/` free of ComfyUI-facing
imports, and that module pulls in aiohttp.

Five existing path assertions moved to the new location. They were asserting
the behaviour this changes, and now carry a note saying why the subfolder is
expected so nobody "fixes" them back.
2026-08-07 14:50:17 +03:00

145 lines
6.4 KiB
Python

"""Tests for resolve_db_path_with_source — the source-aware resolver."""
from __future__ import annotations
from engine.db.connection import (
global_location_path,
resolve_db_path_with_source,
root_location_path,
user_location_path,
)
def test_env_override_returns_source_wp_db_path(monkeypatch, tmp_path):
target = tmp_path / "custom.db"
monkeypatch.setenv("WP_DB_PATH", str(target))
monkeypatch.delenv("COMFYUI_USER_DIR", raising=False)
path, source = resolve_db_path_with_source()
assert path == target
assert source == "WP_DB_PATH"
def test_comfyui_user_dir_env(monkeypatch, tmp_path):
monkeypatch.delenv("WP_DB_PATH", raising=False)
monkeypatch.setenv("COMFYUI_USER_DIR", str(tmp_path))
path, source = resolve_db_path_with_source()
assert path == tmp_path / "wildcard-pipeline.db"
assert source == "COMFYUI_USER_DIR"
def test_legacy_fallback_when_no_comfyui_detected(monkeypatch, tmp_path):
monkeypatch.delenv("WP_DB_PATH", raising=False)
monkeypatch.delenv("COMFYUI_USER_DIR", raising=False)
# Force both detectors to return None
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_api", lambda: None)
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_path", lambda: None)
monkeypatch.setattr("engine.db.connection._load_sidecar", lambda: {})
path, source = resolve_db_path_with_source()
# Source is now "global" — the design folds the old "legacy" label into
# "global" because both resolve to ~/.comfyui/wildcard-pipeline.db and
# the new UI exposes a "Global" preference.
assert source == "global"
assert path.name == "wildcard-pipeline.db"
assert ".comfyui" in str(path)
def test_comfyui_user_dir_resolved(monkeypatch, tmp_path):
monkeypatch.delenv("WP_DB_PATH", raising=False)
monkeypatch.delenv("COMFYUI_USER_DIR", raising=False)
user_dir = tmp_path / "user"
user_dir.mkdir()
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_api", lambda: user_dir)
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_path", lambda: None)
monkeypatch.setattr("engine.db.connection._load_sidecar", lambda: {})
path, source = resolve_db_path_with_source()
assert path == user_dir / "wildcard-pipeline" / "wildcard-pipeline.db"
# Source is now "user" — the design renames the old "comfyui_user_dir"
# label to "user" so it matches the UI radio button.
assert source == "user"
def test_sidecar_user_preference(monkeypatch, tmp_path):
monkeypatch.delenv("WP_DB_PATH", raising=False)
monkeypatch.delenv("COMFYUI_USER_DIR", raising=False)
user_dir = tmp_path / "user"
user_dir.mkdir()
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_api", lambda: user_dir)
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_path", lambda: None)
# Stub the sidecar loader so the test doesn't touch the real file.
monkeypatch.setattr("engine.db.connection._load_sidecar", lambda: {"preference": "user"})
path, source = resolve_db_path_with_source()
assert source == "user"
assert path == user_dir / "wildcard-pipeline" / "wildcard-pipeline.db"
def test_sidecar_global_preference(monkeypatch):
monkeypatch.delenv("WP_DB_PATH", raising=False)
monkeypatch.delenv("COMFYUI_USER_DIR", raising=False)
monkeypatch.setattr("engine.db.connection._load_sidecar", lambda: {"preference": "global"})
path, source = resolve_db_path_with_source()
assert source == "global"
assert ".comfyui" in str(path)
def test_sidecar_root_preference(monkeypatch):
monkeypatch.delenv("WP_DB_PATH", raising=False)
monkeypatch.delenv("COMFYUI_USER_DIR", raising=False)
monkeypatch.setattr("engine.db.connection._load_sidecar", lambda: {"preference": "root"})
path, source = resolve_db_path_with_source()
assert source == "root"
assert path.name == "wildcard-pipeline.db"
assert path.parent.name == "db"
def test_env_wins_over_sidecar(monkeypatch, tmp_path):
monkeypatch.setenv("WP_DB_PATH", str(tmp_path / "x.db"))
monkeypatch.setattr("engine.db.connection._load_sidecar", lambda: {"preference": "global"})
path, source = resolve_db_path_with_source()
assert source == "WP_DB_PATH"
# ---------------------------------------------------------------------------
# Location path helpers — exposed for the SPA Settings API so the frontend
# can list all three potential DB locations regardless of which one wins
# resolution. They take no args and never read env vars / sidecar.
# ---------------------------------------------------------------------------
# The `user` location lives inside our own `wildcard-pipeline/` subfolder, not
# loose in ComfyUI's user directory. The database and the tag list are two files
# now, and `engine/db/relocate.py` moves pre-existing installs in on first boot —
# so these paths and that mover have to agree.
def test_user_location_path_returns_path_when_detected(monkeypatch, tmp_path):
user_dir = tmp_path / "user"
user_dir.mkdir()
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_api", lambda: user_dir)
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_path", lambda: None)
assert user_location_path() == user_dir / "wildcard-pipeline" / "wildcard-pipeline.db"
def test_user_location_path_falls_back_to_path_detector(monkeypatch, tmp_path):
user_dir = tmp_path / "user"
user_dir.mkdir()
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_api", lambda: None)
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_path", lambda: user_dir)
assert user_location_path() == user_dir / "wildcard-pipeline" / "wildcard-pipeline.db"
def test_user_location_path_returns_none_when_undetected(monkeypatch):
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_api", lambda: None)
monkeypatch.setattr("engine.db.connection._comfyui_user_dir_from_path", lambda: None)
assert user_location_path() is None
def test_global_location_path_under_home_dot_comfyui():
path = global_location_path()
assert path.name == "wildcard-pipeline.db"
assert path.parent.name == ".comfyui"
def test_root_location_path_under_plugin_db_dir():
path = root_location_path()
assert path.name == "wildcard-pipeline.db"
assert path.parent.name == "db"
# Sanity: the parent of <plugin>/db/ must be the plugin root.
assert (path.parent.parent / "engine").is_dir()