test: close every database before unlinking and anchor public paths in image tests

Each owned test that opens a PromptDatabase now calls close_all() before
removing the file, and temp directories holding a database use
ignore_cleanup_errors, so the suite tears down cleanly on Windows. The
image API fixture lays out a fake ComfyUI tree (<tmp>/output) and pins
GalleryConfig.path_anchors to it so responses can be checked for
root-relative paths.
This commit is contained in:
Vito Sansevero
2026-10-08 18:01:34 -07:00
parent f9da6488d6
commit 507c8a8993
8 changed files with 49 additions and 23 deletions
+2 -2
View File
@@ -45,7 +45,7 @@ class BackupRestoreTestCase(AioHTTPTestCase):
return app
async def tearDownAsync(self):
self.api.db.close()
self.api.db.close_all()
for name in os.listdir(self.tmpdir):
os.unlink(os.path.join(self.tmpdir, name))
os.rmdir(self.tmpdir)
@@ -66,7 +66,7 @@ class BackupRestoreTestCase(AioHTTPTestCase):
db = PromptDatabase(path)
for text in texts:
db.save_prompt(text=text, prompt_hash=generate_prompt_hash(text))
db.close()
db.close_all()
return path
async def _restore(self, path, field="database_file"):
+15 -2
View File
@@ -27,6 +27,7 @@ from database.operations import PromptDatabase # noqa: E402
import py.api.images as images_module # noqa: E402
from py.api import PromptManagerAPI # noqa: E402
from py.api.prompts import MAX_PAGE_LIMIT # noqa: E402
from py.config import GalleryConfig # noqa: E402
from utils.hashing import generate_prompt_hash # noqa: E402
@@ -46,8 +47,17 @@ class ImageAPITestCase(AioHTTPTestCase):
self._temp_db = tempfile.NamedTemporaryFile(delete=False, suffix=".db")
self._temp_db.close()
self._output_tmp = tempfile.TemporaryDirectory()
self.output_dir = Path(os.path.realpath(self._output_tmp.name))
self._output_tmp = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
# A fake ComfyUI tree: <tmp>/output is the gallery root and <tmp> is the
# anchor that public (response) paths are rendered relative to.
self.comfy_dir = Path(os.path.realpath(self._output_tmp.name))
self.output_dir = self.comfy_dir / "output"
self.output_dir.mkdir()
anchors = [os.path.normcase(str(self.comfy_dir))]
self._anchor_patch = patch.object(
GalleryConfig, "path_anchors", classmethod(lambda cls: list(anchors))
)
self._anchor_patch.start()
app = web.Application()
routes = web.RouteTableDef()
@@ -61,6 +71,9 @@ class ImageAPITestCase(AioHTTPTestCase):
return app
async def tearDownAsync(self):
self._anchor_patch.stop()
# Windows refuses to unlink a database that still has open handles.
self.api.db.close_all()
for path in (
self._temp_db.name,
self._temp_db.name + "-wal",
+2
View File
@@ -43,6 +43,8 @@ class PromptAPITestCase(AioHTTPTestCase):
return app
async def tearDownAsync(self):
# Windows refuses to unlink a database that still has open handles.
self.api.db.close_all()
for path in (
self._temp_db.name,
self._temp_db.name + "-wal",
+6 -3
View File
@@ -31,7 +31,7 @@ class DatabaseTestCase(unittest.TestCase):
self.db = PromptDatabase(self.temp_db.name)
def tearDown(self):
self.db.close()
self.db.close_all()
for suffix in ("", "-wal", "-shm"):
path = self.temp_db.name + suffix
if os.path.exists(path):
@@ -341,14 +341,17 @@ class TestPagination(DatabaseTestCase):
# Use a fresh empty DB
empty_db_file = tempfile.NamedTemporaryFile(delete=False, suffix=".db")
empty_db_file.close()
empty_db = PromptDatabase(empty_db_file.name)
try:
empty_db = PromptDatabase(empty_db_file.name)
result = empty_db.get_recent_prompts(limit=10, offset=0)
self.assertEqual(result["total"], 0)
self.assertEqual(len(result["prompts"]), 0)
self.assertFalse(result["has_more"])
finally:
os.unlink(empty_db_file.name)
empty_db.close_all()
for suffix in ("", "-wal", "-shm"):
if os.path.exists(empty_db_file.name + suffix):
os.unlink(empty_db_file.name + suffix)
def test_total_count_is_integer(self):
result = self.db.get_recent_prompts(limit=5)
+3 -3
View File
@@ -45,7 +45,7 @@ class BackupTestCase(unittest.TestCase):
self.addCleanup(self._cleanup)
def _cleanup(self):
self.db.close()
self.db.close_all()
for name in os.listdir(self.tmpdir):
os.unlink(os.path.join(self.tmpdir, name))
os.rmdir(self.tmpdir)
@@ -59,7 +59,7 @@ class BackupTestCase(unittest.TestCase):
src = PromptDatabase(src_path)
for text in texts:
self._save(src, text)
src.close()
src.close_all()
return src_path
@@ -230,7 +230,7 @@ class TestRestoreFromFile(BackupTestCase):
def test_restore_without_a_live_database_returns_empty_backup_path(self):
src = self._make_source_db(["fresh"])
self.db.close()
self.db.close_all()
os.unlink(self.path)
for suffix in ("-wal", "-shm"):
if os.path.exists(self.path + suffix):
+1 -1
View File
@@ -29,7 +29,7 @@ class ConcurrencyTestCase(unittest.TestCase):
self.addCleanup(self._cleanup)
def _cleanup(self):
self.db.close()
self.db.close_all()
for suffix in ("", "-wal", "-shm"):
if os.path.exists(self.path + suffix):
os.unlink(self.path + suffix)
+3 -3
View File
@@ -42,7 +42,7 @@ class MigrationTestCase(unittest.TestCase):
def _cleanup(self):
for db in self.dbs:
db.close()
db.close_all()
PromptModel.reset_schema_cache()
for name in os.listdir(self.tmpdir):
os.unlink(os.path.join(self.tmpdir, name))
@@ -105,7 +105,7 @@ class TestSchemaInitOncePerProcess(MigrationTestCase):
def test_deleted_database_file_is_recreated_on_next_construction(self):
db = self._open()
db.close()
db.close_all()
os.unlink(self.path)
for suffix in ("-wal", "-shm"):
if os.path.exists(self.path + suffix):
@@ -118,7 +118,7 @@ class TestSchemaInitOncePerProcess(MigrationTestCase):
def test_close_then_unlink_succeeds(self):
db = self._open()
db.save_prompt(text="x", prompt_hash="x")
db.close()
db.close_all()
os.unlink(self.path)
self.assertFalse(os.path.exists(self.path))
+17 -9
View File
@@ -22,7 +22,7 @@ class UsageTestCase(unittest.TestCase):
def tearDown(self):
if getattr(self, "db", None) is not None:
self.db.close()
self.db.close_all()
for suffix in ("", "-wal", "-shm"):
if os.path.exists(self.path + suffix):
os.unlink(self.path + suffix)
@@ -164,9 +164,11 @@ class TestUsageMigration(unittest.TestCase):
def setUp(self):
fd, self.path = tempfile.mkstemp(suffix=".db")
os.close(fd)
db = PromptDatabase(self.path)
self.dbs = []
db = self._open()
self.with_images = db.save_prompt(text="has images", prompt_hash="h1")
self.no_images = db.save_prompt(text="no images", prompt_hash="h2")
db.close_all()
with sqlite3.connect(self.path) as conn:
conn.execute(
"UPDATE prompts SET created_at = '2026-01-01T10:00:00.000000+00:00'"
@@ -188,15 +190,21 @@ class TestUsageMigration(unittest.TestCase):
# must look at the file again to see the downgraded schema.
PromptModel.reset_schema_cache()
def _open(self):
"""Open the database under test and close it again in tearDown."""
db = PromptDatabase(self.path)
self.dbs.append(db)
return db
def tearDown(self):
if getattr(self, "db", None) is not None:
self.db.close()
for db in self.dbs:
db.close_all()
for suffix in ("", "-wal", "-shm"):
if os.path.exists(self.path + suffix):
os.unlink(self.path + suffix)
def test_backfill_estimates_usage_from_linked_images(self):
db = PromptDatabase(self.path)
db = self._open()
used = db.get_prompt_by_id(self.with_images)
unused = db.get_prompt_by_id(self.no_images)
@@ -206,7 +214,7 @@ class TestUsageMigration(unittest.TestCase):
self.assertTrue(unused["last_used_at"].startswith("2026-01-01T10:00:00"))
def test_backfill_runs_only_when_the_columns_are_added(self):
db = PromptDatabase(self.path)
db = self._open()
# A row whose usage is unknown (e.g. written by 3.2.3 after a downgrade)
# stays "never run": a restart must not invent a run for it, otherwise
# Recently Used could never tell saved-but-unused prompts from run ones.
@@ -221,14 +229,14 @@ class TestUsageMigration(unittest.TestCase):
self.assertIsNone(db.get_prompt_by_id(legacy_id)["last_used_at"])
PromptModel.reset_schema_cache()
later = PromptDatabase(self.path).get_prompt_by_id(legacy_id)
later = self._open().get_prompt_by_id(legacy_id)
self.assertIsNone(later["last_used_at"])
self.assertEqual(later["run_count"], 0)
def test_migration_runs_once(self):
db = PromptDatabase(self.path)
db = self._open()
db.record_prompt_use(self.no_images)
reopened = PromptDatabase(self.path)
reopened = self._open()
self.assertEqual(reopened.get_prompt_by_id(self.no_images)["run_count"], 2)