* feat(history): normalize bounded execution evidence (#209) * fix(history): bound lifecycle kind checks before hashing (#209) * feat(history): persist execution attempts with restore fencing (#209) * test(api): bootstrap standalone backup restore fixtures (#209) * feat(history): isolate bounded asynchronous ledger writes (#209) * feat(history): observe actual queue attempts without altering execution (#209) * fix(database): honor package-scoped configuration (#211) * test(history): exercise real cached and headless executions (#209) * fix(history): reject unsupported uniqueness and isolate package fixtures (#209) * test(history): support Python 3.10 package fixture cleanup (#209)
154 lines
6.1 KiB
Python
154 lines
6.1 KiB
Python
"""
|
|
Tests for issue #119: Database path configuration.
|
|
|
|
Verifies that _resolve_db_path() correctly reads from config, resolves
|
|
relative paths against the extension root, and handles absolute paths.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import types
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import database.operations as operations
|
|
from database.operations import _resolve_db_path
|
|
|
|
EXTENSION_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
class TestResolveDbPath(unittest.TestCase):
|
|
"""Test _resolve_db_path() database path resolution."""
|
|
|
|
def test_package_qualified_config_precedes_unrelated_standalone_config(
|
|
self,
|
|
) -> None:
|
|
"""A custom-node package must use its own configured disposable database."""
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
configured = os.path.join(directory, "owned", "prompts.db")
|
|
package_config = types.ModuleType("owned_promptmanager.py.config")
|
|
package_config.PromptManagerConfig = types.SimpleNamespace(
|
|
DEFAULT_DB_PATH=configured
|
|
)
|
|
standalone_config = types.ModuleType("py.config")
|
|
standalone_config.PromptManagerConfig = types.SimpleNamespace(
|
|
DEFAULT_DB_PATH=os.path.join(directory, "unrelated.db")
|
|
)
|
|
with (
|
|
patch.dict(
|
|
operations.__dict__,
|
|
{"__package__": "owned_promptmanager.database", "__spec__": None},
|
|
),
|
|
patch.dict(
|
|
sys.modules,
|
|
{
|
|
"owned_promptmanager.py.config": package_config,
|
|
"py.config": standalone_config,
|
|
},
|
|
),
|
|
):
|
|
resolved = _resolve_db_path(None)
|
|
self.assertEqual(resolved, configured)
|
|
self.assertTrue(os.path.isdir(os.path.dirname(configured)))
|
|
|
|
def test_explicit_absolute_path_used_as_is(self):
|
|
"""An explicit absolute path should be returned unchanged."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
path = os.path.join(tmpdir, "custom.db")
|
|
result = _resolve_db_path(path)
|
|
self.assertEqual(result, path)
|
|
|
|
def test_explicit_relative_path_resolved_to_extension_root(self):
|
|
"""An explicit relative path should resolve against the extension root."""
|
|
result = _resolve_db_path("data/my.db")
|
|
expected = os.path.join(EXTENSION_ROOT, "data", "my.db")
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_none_falls_back_to_default_without_config(self):
|
|
"""Without config (outside ComfyUI), should default to prompts.db."""
|
|
# py.config requires ComfyUI's server module, so import fails naturally
|
|
result = _resolve_db_path(None)
|
|
expected = os.path.join(EXTENSION_ROOT, "prompts.db")
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_none_reads_config_when_available(self):
|
|
"""None should read DEFAULT_DB_PATH from config when importable."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
custom_path = os.path.join(tmpdir, "prompts.db")
|
|
fake_config_mod = types.ModuleType("py.config")
|
|
fake_pm_config = type(
|
|
"PromptManagerConfig", (), {"DEFAULT_DB_PATH": custom_path}
|
|
)
|
|
fake_config_mod.PromptManagerConfig = fake_pm_config
|
|
|
|
with patch.dict(sys.modules, {"py.config": fake_config_mod}):
|
|
result = _resolve_db_path(None)
|
|
|
|
self.assertEqual(result, custom_path)
|
|
|
|
def test_config_relative_path_resolved_to_extension_root(self):
|
|
"""A relative path from config should resolve against extension root."""
|
|
fake_config_mod = types.ModuleType("py.config")
|
|
fake_pm_config = type(
|
|
"PromptManagerConfig", (), {"DEFAULT_DB_PATH": "custom_data/prompts.db"}
|
|
)
|
|
fake_config_mod.PromptManagerConfig = fake_pm_config
|
|
|
|
with patch.dict(sys.modules, {"py.config": fake_config_mod}):
|
|
result = _resolve_db_path(None)
|
|
|
|
expected = os.path.join(EXTENSION_ROOT, "custom_data", "prompts.db")
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_result_is_always_absolute(self):
|
|
"""Result should always be an absolute path."""
|
|
result = _resolve_db_path("prompts.db")
|
|
self.assertTrue(os.path.isabs(result))
|
|
|
|
def test_parent_directory_created(self):
|
|
"""Parent directories should be created for custom paths."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
nested = os.path.join(tmpdir, "deep", "nested", "prompts.db")
|
|
result = _resolve_db_path(nested)
|
|
self.assertEqual(result, nested)
|
|
self.assertTrue(os.path.isdir(os.path.join(tmpdir, "deep", "nested")))
|
|
|
|
|
|
class TestPromptDatabaseUsesConfig(unittest.TestCase):
|
|
"""Test that PromptDatabase() picks up the resolved path."""
|
|
|
|
@patch("database.operations._resolve_db_path")
|
|
def test_constructor_calls_resolve_with_none(self, mock_resolve):
|
|
"""PromptDatabase() should call _resolve_db_path with None."""
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".db") as f:
|
|
mock_resolve.return_value = f.name
|
|
|
|
try:
|
|
from database.operations import PromptDatabase
|
|
|
|
PromptDatabase()
|
|
mock_resolve.assert_called_once_with(None)
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
@patch("database.operations._resolve_db_path")
|
|
def test_constructor_passes_explicit_path(self, mock_resolve):
|
|
"""PromptDatabase(path) should call _resolve_db_path with that path."""
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".db") as f:
|
|
mock_resolve.return_value = f.name
|
|
|
|
try:
|
|
from database.operations import PromptDatabase
|
|
|
|
PromptDatabase("/explicit/path.db")
|
|
mock_resolve.assert_called_once_with("/explicit/path.db")
|
|
finally:
|
|
os.unlink(f.name)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|