Files
darth-veitcher-comfydv/tests/test_packaging.py
T
James VeitchandClaude Sonnet 5 09fe013ff9 fix(tests): update Dockerfile test to expect python:3.13-slim
docker/Dockerfile was bumped from 3.11 to 3.13 in 50076d7 without
updating the assertion, breaking the suite on main. Verified 3.13
compatibility by building the image and booting ComfyUI 0.27.0 in it
(custom node loads, DB migrations run, no errors) before updating the
pinned version the test enforces.

Fixes #20

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YArD9ZjBWKsAvazmS48amA
2026-07-22 20:20:33 +01:00

218 lines
7.2 KiB
Python

"""Packaging correctness tests for specs/002-manager-compatible-install."""
import tomllib
from pathlib import Path
REPO_ROOT = Path(__file__).parent.parent
REQUIREMENTS = REPO_ROOT / "requirements.txt"
PYPROJECT = REPO_ROOT / "pyproject.toml"
BANNED_PACKAGES = {"colorama", "termcolor", "rich"}
def _requirements_lines() -> list[str]:
return [
line.strip()
for line in REQUIREMENTS.read_text().splitlines()
if line.strip() and not line.strip().startswith("#")
]
def _pyproject_runtime_deps() -> list[str]:
data = tomllib.loads(PYPROJECT.read_text())
return data.get("project", {}).get("dependencies", [])
class TestRequirementsTxt:
"""US1 — Clean dependency install from a fresh clone."""
def test_requirements_txt_exists(self):
assert REQUIREMENTS.exists(), "requirements.txt not found at repo root"
def test_requirements_txt_contains_jinja2(self):
lines = _requirements_lines()
assert any("jinja2" in line.lower() for line in lines), (
f"jinja2 not found in requirements.txt; got: {lines}"
)
def test_requirements_txt_has_no_removed_packages(self):
lines = _requirements_lines()
for line in lines:
pkg = line.split("=")[0].split(">")[0].split("<")[0].split("[")[0].lower()
assert pkg not in BANNED_PACKAGES, (
f"Removed package {pkg!r} still in requirements.txt"
)
def test_requirements_txt_has_no_bare_dot(self):
lines = _requirements_lines()
assert "." not in lines, (
"requirements.txt contains bare '.' (editable install marker) — remove it"
)
def test_requirements_txt_packages_in_pyproject_dependencies(self):
req_lines = _requirements_lines()
runtime_deps = [
d.split(">=")[0].split("==")[0].split("!=")[0].split("[")[0].lower()
for d in _pyproject_runtime_deps()
]
for line in req_lines:
pkg = line.split("=")[0].split(">")[0].split("<")[0].split("[")[0].lower()
assert pkg in runtime_deps, (
f"Package {pkg!r} is in requirements.txt but not in pyproject.toml [project.dependencies]"
)
def test_aiohttp_in_project_dependencies(self):
runtime_deps = [
d.split(">=")[0].split("==")[0].lower() for d in _pyproject_runtime_deps()
]
assert "aiohttp" in runtime_deps, (
"aiohttp must be in pyproject.toml [project.dependencies], not only in [dependency-groups]"
)
MANAGER_ENTRY = REPO_ROOT / "comfy-manager-entry.json"
MANAGER_REQUIRED_FIELDS = {
"author",
"title",
"reference",
"files",
"install_type",
"description",
"nodename",
}
EXPECTED_INSTALL_TYPE = "git-clone"
EXPECTED_NODENAMES = {
# Original nodes
"Format String (Python f-strings)",
"Random Choice",
"Circuit Breaker",
# Ollama integration nodes (spec 006)
"Ollama Client",
"LLM Model Selector",
"LLM Load Model",
"LLM Unload Model",
"Chat Completion",
"Ollama Option — Temperature",
"Ollama Option — Seed",
"Ollama Option — Max Tokens",
"Ollama Option — Top P",
"Ollama Option — Top K",
"Ollama Option — Repeat Penalty",
"Ollama Option — Extra Body",
"Ollama Debug History",
"Ollama History Length",
}
class TestManagerEntry:
"""US2 — ComfyUI Manager registration."""
def test_manager_entry_json_exists(self):
assert MANAGER_ENTRY.exists(), (
"comfy-manager-entry.json not found at repo root — run T020-I to create it"
)
def test_manager_entry_json_is_valid(self):
import json
data = json.loads(MANAGER_ENTRY.read_text())
assert isinstance(data, dict), "comfy-manager-entry.json must be a JSON object"
def test_manager_entry_has_required_fields(self):
import json
data = json.loads(MANAGER_ENTRY.read_text())
missing = MANAGER_REQUIRED_FIELDS - set(data.keys())
assert not missing, f"Manager entry is missing required fields: {missing}"
assert data.get("install_type") == EXPECTED_INSTALL_TYPE, (
f"install_type must be {EXPECTED_INSTALL_TYPE!r}, got {data.get('install_type')!r}"
)
def test_manager_entry_nodenames_match_node_class_mappings(self):
import json
data = json.loads(MANAGER_ENTRY.read_text())
nodenames = set(data.get("nodename", []))
assert nodenames == EXPECTED_NODENAMES, (
f"nodename list mismatch.\nExpected: {EXPECTED_NODENAMES}\nGot: {nodenames}"
)
ROOT_INIT = REPO_ROOT / "__init__.py"
STALE_PHRASES = ["model memory", "model unloader", "Model Unloader", "Model Memory"]
EXPECTED_NODE_MENTIONS = ["format", "random", "circuit"]
def _root_description() -> str:
text = ROOT_INIT.read_text()
for line in text.splitlines():
if "@description" in line:
return line
return ""
class TestMetadata:
"""US3 — Accurate package metadata."""
def test_description_does_not_mention_model_unloader(self):
desc = _root_description().lower()
assert "model unloader" not in desc, (
"root __init__.py @description references non-existent 'Model Unloader' node"
)
def test_description_does_not_mention_model_memory_management(self):
desc = _root_description().lower()
assert "model memory" not in desc, (
"root __init__.py @description references non-existent 'model memory management'"
)
def test_description_mentions_existing_nodes(self):
desc = _root_description().lower()
for keyword in EXPECTED_NODE_MENTIONS:
assert keyword in desc, (
f"@description should mention the {keyword!r} node but doesn't: {desc!r}"
)
COMPOSE_FILE = REPO_ROOT / "docker-compose.yml"
DOCKERFILE = REPO_ROOT / "docker" / "Dockerfile"
class TestDockerCompose:
"""US4 — Docker Compose local test harness."""
def test_docker_compose_yml_exists(self):
assert COMPOSE_FILE.exists(), (
"docker-compose.yml not found at repo root — run T040-I to create it"
)
def test_docker_compose_has_comfyui_service(self):
import yaml
data = yaml.safe_load(COMPOSE_FILE.read_text())
services = data.get("services", {})
assert "comfyui" in services, (
f"docker-compose.yml must define a 'comfyui' service; found: {list(services.keys())}"
)
def test_docker_compose_exposes_port_8188(self):
import yaml
data = yaml.safe_load(COMPOSE_FILE.read_text())
ports = data.get("services", {}).get("comfyui", {}).get("ports", [])
port_strings = [str(p) for p in ports]
assert any("8188" in p for p in port_strings), (
f"comfyui service must expose port 8188; found ports: {port_strings}"
)
def test_dockerfile_exists(self):
assert DOCKERFILE.exists(), (
"docker/Dockerfile not found — run T041-I to create it"
)
def test_dockerfile_uses_python_313_base(self):
text = DOCKERFILE.read_text()
assert "FROM python:3.13" in text, (
"Dockerfile must use python:3.13 base image (CPU-only, no CUDA)"
)