* feat(db): add pair schema and stage restore migrations for #193 * feat(database): persist reusable ordered prompt pairs (#193) * feat(api): expose reusable pairs and atomic metadata enrichment (#193) * feat(metadata): resolve canonical prompt pairs with bounded provenance (#193) * feat(scan): enrich image pairs without guessing or duplicate links (#193) * fix(database): preserve pair tags during global maintenance (#193) * feat(tracking): enrich live image pairs and cancel scans safely (#193) * feat(nodes): encode paired prompts and count cached recipe runs (#193) * fix(database): validate restored pair ownership and provenance (#193) * feat(admin): report pair scan outcomes and cancellation (#193) * feat(ui): add paired prompt editor and explicit recipe library for #193 * fix(autocomplete): include reusable pair metadata vocabulary (#193) * fix(pairs): honor recent sorting and conservative scan outcomes (#193) * fix(ui): protect paired draft saves and clarify negative variants (#193) * fix(metadata): preserve bounded paired execution evidence (#193) * fix(tracking): follow actual pair output roles and prefer execution evidence (#193) * docs(pairs): document live validation and retain renderer screenshots (#193) * test(api): inject disposable databases before API startup (#194) * test(api): canonicalize portable temporary gallery roots (#194) * docs(ui): prototype branching negative variant history (#193) * docs(ui): show selected negative beneath positive in timeline concept (#193) * docs: retain approved timeline for later gallery redesign (#193) * test(db): close restore fixture connections before cleanup (#194) * test(db): reuse existing handle guards in all pair fixtures (#194) * chore: clarify rollback test and intentional exception handling (#193)
62 lines
2.4 KiB
Python
62 lines
2.4 KiB
Python
"""Live metadata annotation uses the same conservative ingestion as scan."""
|
|
|
|
import logging
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from tests.open_handles import assert_closed
|
|
except ImportError: # discovered with tests/ as the top-level directory
|
|
from open_handles import assert_closed
|
|
|
|
from database.operations import PromptDatabase
|
|
from utils.hashing import generate_prompt_hash
|
|
from utils.image_monitor import ImageGenerationHandler
|
|
|
|
|
|
def metadata(positive="cat", negative="blur"):
|
|
return {
|
|
"prompt": {
|
|
"1": {"class_type": "PromptManager", "inputs": {"text": positive}},
|
|
"2": {"class_type": "CLIPTextEncode", "inputs": {"text": negative}},
|
|
"3": {
|
|
"class_type": "KSampler",
|
|
"inputs": {"positive": ["1", 0], "negative": ["2", 0]},
|
|
},
|
|
}
|
|
}
|
|
|
|
|
|
class TestPairImageLinking(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.db = PromptDatabase(str(Path(self.temp.name) / "db.sqlite"))
|
|
self.path = str(Path(self.temp.name) / "image.png")
|
|
self.pid = self.db.save_prompt("cat", prompt_hash=generate_prompt_hash("cat"))
|
|
self.handler = ImageGenerationHandler.__new__(ImageGenerationHandler)
|
|
self.handler.db_manager = self.db
|
|
self.handler.logger = logging.getLogger("test.pairlink")
|
|
|
|
def tearDown(self):
|
|
self.db.close_all()
|
|
assert_closed(self, self.temp.name)
|
|
self.temp.cleanup()
|
|
|
|
def test_exact_metadata_creates_pair_link_without_counting_a_run(self):
|
|
self.handler.link_image_to_prompt(self.path, {"id": self.pid}, metadata())
|
|
self.handler.link_image_to_prompt(self.path, {"id": self.pid}, metadata())
|
|
pairs = self.db.list_prompt_pairs()["pairs"]
|
|
self.assertEqual(len(pairs), 1)
|
|
self.assertEqual(pairs[0]["image_count"], 1)
|
|
self.assertEqual(pairs[0]["run_count"], 0)
|
|
|
|
def test_stale_context_or_ambiguous_metadata_does_not_create_pair(self):
|
|
meta = metadata(positive="dog")
|
|
self.handler.link_image_to_prompt(self.path, {"id": self.pid}, meta)
|
|
self.assertEqual(self.db.list_prompt_pairs()["total"], 0)
|
|
meta = metadata()
|
|
meta["prompt"]["4"] = meta["prompt"]["3"]
|
|
self.handler.link_image_to_prompt(self.path + "2", {"id": self.pid}, meta)
|
|
self.assertEqual(self.db.list_prompt_pairs()["total"], 0)
|