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.
91 lines
3.5 KiB
Python
91 lines
3.5 KiB
Python
"""Tests for SQLite connection factory."""
|
|
import sqlite3
|
|
|
|
from engine.db.connection import get_connection, resolve_db_path
|
|
|
|
|
|
def test_resolve_db_path_uses_override(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("WP_DB_PATH", str(tmp_path / "custom.db"))
|
|
assert resolve_db_path() == tmp_path / "custom.db"
|
|
|
|
|
|
def test_resolve_db_path_default_under_user_dir(tmp_path, monkeypatch):
|
|
monkeypatch.delenv("WP_DB_PATH", raising=False)
|
|
monkeypatch.setenv("COMFYUI_USER_DIR", str(tmp_path))
|
|
assert resolve_db_path() == tmp_path / "wildcard-pipeline.db"
|
|
|
|
|
|
def test_get_connection_returns_sqlite3_connection(tmp_path):
|
|
conn = get_connection(tmp_path / "test.db")
|
|
assert isinstance(conn, sqlite3.Connection)
|
|
conn.close()
|
|
|
|
|
|
def test_get_connection_enables_wal_mode(tmp_path):
|
|
conn = get_connection(tmp_path / "wal.db")
|
|
mode = conn.execute("PRAGMA journal_mode;").fetchone()[0]
|
|
assert mode == "wal"
|
|
conn.close()
|
|
|
|
|
|
def test_get_connection_enforces_foreign_keys(tmp_path):
|
|
conn = get_connection(tmp_path / "fk.db")
|
|
fk = conn.execute("PRAGMA foreign_keys;").fetchone()[0]
|
|
assert fk == 1
|
|
conn.close()
|
|
|
|
|
|
def test_get_connection_creates_parent_dir(tmp_path):
|
|
target = tmp_path / "nested" / "deeper" / "wp.db"
|
|
conn = get_connection(target)
|
|
assert target.parent.exists()
|
|
conn.close()
|
|
|
|
|
|
def test_get_connection_row_factory_supports_name_access(tmp_path):
|
|
conn = get_connection(tmp_path / "row.db")
|
|
conn.execute("CREATE TABLE t (val INTEGER);")
|
|
conn.execute("INSERT INTO t VALUES (42);")
|
|
row = conn.execute("SELECT val FROM t;").fetchone()
|
|
assert row["val"] == 42
|
|
conn.close()
|
|
|
|
|
|
def test_resolve_db_path_prefers_comfyui_user_dir(monkeypatch, tmp_path):
|
|
"""When ComfyUI's user directory is detected AND no sidecar
|
|
preference overrides it, the resolver should land in
|
|
`<ComfyUI>/user/wildcard-pipeline.db`."""
|
|
from engine.db import connection as conn_mod
|
|
|
|
monkeypatch.delenv("WP_DB_PATH", raising=False)
|
|
monkeypatch.delenv("COMFYUI_USER_DIR", raising=False)
|
|
# Fix the detected ComfyUI user dir to a clean tmp path so we
|
|
# don't depend on whatever the path-traversal finds in the host
|
|
# ComfyUI install. Also stub the sidecar loader so the test
|
|
# doesn't depend on whatever db-config.json sits in the plugin
|
|
# dir (and so it returns the default "user" path, not "global"
|
|
# or "root").
|
|
fake_comfy_user = tmp_path / "comfy-user"
|
|
monkeypatch.setattr(conn_mod, "_comfyui_user_dir_from_api", lambda: None)
|
|
monkeypatch.setattr(conn_mod, "_comfyui_user_dir_from_path", lambda: fake_comfy_user)
|
|
monkeypatch.setattr(conn_mod, "_load_sidecar", lambda: {})
|
|
result = resolve_db_path()
|
|
assert result == fake_comfy_user / "wildcard-pipeline" / "wildcard-pipeline.db"
|
|
|
|
|
|
def test_resolve_db_path_falls_back_to_home_when_no_comfyui_root(monkeypatch):
|
|
"""Standalone install (no ComfyUI on the path). Both detectors
|
|
return None; we fall through to ~/.comfyui (reported as "global"
|
|
by the source-aware resolver)."""
|
|
from pathlib import Path
|
|
|
|
from engine.db import connection as conn_mod
|
|
|
|
monkeypatch.delenv("WP_DB_PATH", raising=False)
|
|
monkeypatch.delenv("COMFYUI_USER_DIR", raising=False)
|
|
monkeypatch.setattr(conn_mod, "_comfyui_user_dir_from_api", lambda: None)
|
|
monkeypatch.setattr(conn_mod, "_comfyui_user_dir_from_path", lambda: None)
|
|
monkeypatch.setattr(conn_mod, "_load_sidecar", lambda: {})
|
|
result = resolve_db_path()
|
|
assert result == Path.home() / ".comfyui" / "wildcard-pipeline.db"
|