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

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"