Files

127 lines
5.4 KiB
Python

"""CPU-only release metadata, module documentation, and node-help checks."""
from __future__ import annotations
import ast
import importlib
import json
import re
import sys
import types
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def _install_namespace(name: str, path: Path) -> None:
if name in sys.modules:
return
module = types.ModuleType(name)
module.__path__ = [str(path)]
sys.modules[name] = module
_install_namespace("CaptionForge", ROOT)
_install_namespace("CaptionForge.engines", ROOT / "engines")
_install_namespace("CaptionForge.nodes", ROOT / "nodes")
_install_namespace("CaptionForge.nodes.caption_nodes", ROOT / "nodes" / "caption_nodes")
if "folder_paths" not in sys.modules:
folder_paths = types.ModuleType("folder_paths")
folder_paths.models_dir = str(ROOT / "models")
folder_paths.get_output_directory = lambda: str(ROOT / "output")
sys.modules["folder_paths"] = folder_paths
elif not hasattr(sys.modules["folder_paths"], "models_dir"):
sys.modules["folder_paths"].models_dir = str(ROOT / "models")
version_module = importlib.import_module("CaptionForge.captionforge_version")
planner = importlib.import_module("CaptionForge.nodes.jlc_captionforge_pipeline_planner_node")
capstone = importlib.import_module("CaptionForge.nodes.jlc_captionforge_node")
template_options = importlib.import_module("CaptionForge.nodes.jlc_captionforge_template_options")
joy = importlib.import_module("CaptionForge.nodes.caption_nodes.jlc_captionforge_joy_caption_node")
qwen = importlib.import_module("CaptionForge.nodes.caption_nodes.jlc_captionforge_qwen_caption_node")
ollama = importlib.import_module("CaptionForge.nodes.caption_nodes.jlc_captionforge_ollama_caption_node")
class ReleaseMetadataTests(unittest.TestCase):
def test_current_release_versions_are_1_0_2(self) -> None:
self.assertEqual(version_module.CAPTIONFORGE_VERSION, "1.0.2")
pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
match = re.search(r'^version\s*=\s*"([^"]+)"', pyproject, re.MULTILINE)
self.assertIsNotNone(match)
self.assertEqual(match.group(1), version_module.CAPTIONFORGE_VERSION)
config = json.loads(
(ROOT / "config" / "captionforge_ollama_models.json").read_text(encoding="utf-8")
)
self.assertEqual(config["_meta"]["version"], version_module.CAPTIONFORGE_VERSION)
self.assertEqual(capstone.CAPTIONFORGE_NODE_VERSION, version_module.CAPTIONFORGE_VERSION)
def test_anchor_tooltips_describe_frozen_1_x_behavior(self) -> None:
expected = (
"Optional persistent caption/training anchor. In CaptionForge 1.x, a non-empty "
"anchor is preserved in the final caption variants rather than treated as image "
"evidence that the Validator may remove."
)
self.assertEqual(
planner.JLC_CaptionForge_Pipeline_Planner.INPUT_TYPES()["required"]
["LoRA - user caption anchor"][1]["tooltip"],
expected,
)
self.assertEqual(
capstone.JLC_CaptionForge.INPUT_TYPES()["required"]
["LoRA - user caption anchor"][1]["tooltip"],
expected,
)
def test_package_discovery_is_explicit_and_production_only(self) -> None:
pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
self.assertNotIn("[tool.setuptools.packages.find]", pyproject)
for package in (
"CaptionForge",
"CaptionForge.engines",
"CaptionForge.nodes",
"CaptionForge.nodes.caption_nodes",
):
self.assertIn(f'"{package}"', pyproject)
for excluded in (".backups", ".tests", ".bak", ".deprecated", "experimental"):
self.assertNotIn(f'"{excluded}', pyproject)
def test_active_python_modules_have_module_docstrings(self) -> None:
files = [ROOT / "__init__.py", ROOT / "captionforge_version.py"]
files.extend((ROOT / "engines").glob("*.py"))
files.extend((ROOT / "nodes").glob("*.py"))
files.extend((ROOT / "nodes" / "caption_nodes").glob("*.py"))
files.extend((ROOT / "tests").glob("*.py"))
for path in files:
with self.subTest(path=path.relative_to(ROOT)):
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
self.assertTrue(ast.get_docstring(tree))
class ActiveNodeHelpTests(unittest.TestCase):
def test_every_active_input_has_plain_help_text(self) -> None:
node_classes = (
planner.JLC_CaptionForge_Pipeline_Planner,
capstone.JLC_CaptionForge,
template_options.JLC_CaptionForgeExtraOptions,
joy.JLC_CaptionForgeJoy,
qwen.JLC_CaptionForgeQwen,
ollama.JLC_CaptionForgeOllamaCaption,
)
for node_class in node_classes:
inputs = node_class.INPUT_TYPES()
for section in ("required", "optional"):
for name, spec in inputs.get(section, {}).items():
with self.subTest(node=node_class.__name__, input=name):
self.assertGreaterEqual(len(spec), 2)
metadata = spec[1]
self.assertIsInstance(metadata, dict)
self.assertTrue(str(metadata.get("tooltip", "")).strip())
if __name__ == "__main__":
unittest.main()