Files
ComfyAssets-ComfyUI_PromptM…/tests/test_pair_vocabulary.py
Vito caa9649f32 feat: reuse positive and negative prompt combinations (#195)
* 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)
2026-10-17 08:00:42 -07:00

113 lines
4.6 KiB
Python

"""Autocomplete shares vocabulary without mixing pair and prompt identities."""
import os
import tempfile
import unittest
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
class TestPairVocabulary(unittest.TestCase):
def setUp(self):
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
self.addCleanup(assert_closed, self, directory.name)
self.db = PromptDatabase(os.path.join(directory.name, "vocabulary.db"))
self.addCleanup(self.db.close_all)
def prompt(self, text: str, **kwargs) -> int:
return self.db.save_prompt(
text, prompt_hash=generate_prompt_hash(text), **kwargs
)
def suggestions(self, prefix="", context=None, limit=20):
return self.db.suggest_tags(prefix, context, limit)
def test_pair_only_categories_and_tags_are_available_without_component_metadata(
self,
):
pair = self.db.get_prompt_pair(
self.db.save_prompt_pair(
"portrait", "blurry", category="Pairs", tags=["studio"]
)
)
self.assertEqual(self.db.get_all_categories(), ["Pairs"])
self.assertEqual(self.db.get_all_tags(), ["studio"])
self.assertEqual(self.suggestions("stu"), [{"name": "studio", "count": 1}])
for name in ("positive_prompt_id", "negative_prompt_id"):
component = self.db.get_prompt_by_id(pair[name])
self.assertIsNone(component["category"])
self.assertEqual(component["tags"], [])
def test_categories_are_trimmed_deduplicated_and_include_archived_pairs(self):
self.prompt("standalone", category=" Portrait ")
pair = self.db.save_prompt_pair(
"portrait", "", category="Portrait", tags=["archived"]
)
self.db.archive_prompt_pair(pair)
self.db.save_prompt_pair("landscape", "", category=" Landscape ")
self.db.save_prompt_pair("empty", "", category=" ")
self.assertEqual(self.db.get_all_categories(), ["Landscape", "Portrait"])
self.assertEqual(self.db.get_all_tags(), ["archived"])
self.assertEqual(self.suggestions("arc"), [{"name": "archived", "count": 1}])
def test_counts_include_distinct_pair_and_prompt_references(self):
self.prompt("portrait", tags=["shared", "legacy"])
self.db.save_prompt_pair(
"portrait", "first negative", tags=["shared", "paired"]
)
self.db.save_prompt_pair("portrait", "second negative", tags=["shared"])
self.assertEqual(self.suggestions("sha"), [{"name": "shared", "count": 3}])
self.assertEqual(
self.suggestions("sha", ["paired"]), [{"name": "shared", "count": 1}]
)
self.assertEqual(
self.suggestions("sha", ["legacy"]), [{"name": "shared", "count": 1}]
)
def test_context_does_not_join_colliding_pair_and_prompt_ids(self):
prompt = self.prompt("portrait", tags=["legacy-context", "legacy-only"])
pair = self.db.save_prompt_pair(
"portrait", "blurry", tags=["pair-context", "pair-only"]
)
self.assertEqual(prompt, pair)
self.assertEqual(
self.suggestions("", ["legacy-context"]),
[{"name": "legacy-only", "count": 1}],
)
self.assertEqual(
self.suggestions("", ["pair-context"]), [{"name": "pair-only", "count": 1}]
)
self.assertEqual(self.suggestions("", ["legacy-context", "pair-context"]), [])
def test_pair_context_matches_all_tags_case_insensitively(self):
self.db.save_prompt_pair("portrait", "blurry", tags=["One", "two", "three"])
self.db.save_prompt_pair("other", "blurry", tags=["One", "extra"])
self.assertEqual(
self.suggestions("TH", ["one", "TWO", "one"]),
[{"name": "three", "count": 1}],
)
self.assertEqual(self.suggestions("", ["missing"]), [])
def test_pair_prefix_wildcards_are_literal_and_limit_is_preserved(self):
self.db.save_prompt_pair(
"portrait", "", tags=["tag_one", "tag_two", "tag%literal"]
)
self.assertEqual(
self.suggestions("tag_", limit=1), [{"name": "tag_one", "count": 1}]
)
self.assertEqual(
self.suggestions("tag%"), [{"name": "tag%literal", "count": 1}]
)
self.assertEqual(self.suggestions("%"), [])
if __name__ == "__main__":
unittest.main()