* fix: eliminate all identified broken windows before Ollama implementation - pyproject.toml: add requires-python>=3.11, real description, remove dead [project.scripts] entry (no main() exists), add system/integration markers - __init__.py: replace pytest-in-sys.modules guard with comfy-in-sys.modules (checks the actual condition; cleaner semantics) - tests/conftest.py: remove duplicate module-level class definitions and the aiohttp mock (real aiohttp is installed; mock blocked integration tests) - circuit_breaker.py: remove copy-pasted INPUT_TYPES boilerplate docstring; logger.debug → logger.warning for ComfyUI-absent branch (degraded state) - random_choice.py: remove copy-pasted INPUT_TYPES boilerplate and dead triple-quoted string literal; fix bare re-raise to log before propagating - format_string.py: print() → logger.warning(); remove # type: ignore on two dict assignments; remove cargo-culted # noqa: F401 (ruff doesn't flag) - scripts/take_screenshots.py: remove unused h_pad and v_pad parameters All 76 tests pass; ruff clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * design(006): full DESIGN phase artefacts for Ollama model integration Adds spec, plan, tasks, 6 BDD feature files, ADRs, and epic file for spec 006-ollama-model-integration (14 nodes ported from darth-veitcher/comfyui-ollama-model-manager, Issue #1 fix, logging harmonisation, live-service integration tests). - specs/006-ollama-model-integration/{spec,plan,tasks}.md - specs/006-ollama-model-integration/features/us{1-6}_*.feature - specs/006-ollama-model-integration/{.beacon.toml,checklists/} - project-management/ADRs/ADR-004-aiohttp-over-httpx-for-ollama.md - project-management/ADRs/ADR-005-ollama-host-config-via-client-node.md - project-management/Roadmap/epics/ollama-integration.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * chore(006): Phase 1 setup — Ollama fixtures, Justfile recipes, test skeleton - conftest.py: add ollama_host (session), ollama_available (session), skip_if_no_ollama fixtures for @pytest.mark.integration tests - Justfile: add test, test-unit, test-integration, test-system recipes - tests/test_ollama.py: empty skeleton with module docstring and lazy-import block (uncommented phase-by-phase as ollama.py is built) Closes T001 (system marker — done in broken-windows commit), T002, T003, T004. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * feat(006): Phase 3 foundational — ollama.py, ollama.js, __init__ wiring (T011-T014) - src/comfydv/ollama.py: OllamaClientType, _run_async, _fetch_models, _post_json, _DEFAULT_MODELS (populated at import), /dv/ollama/models route, all 14 node classes (US1-US6) with correct ComfyUI contract - src/js/ollama.js: app.registerExtension for OllamaModelSelector/LoadModel/ ChatCompletion — refreshModelDropdown + ⟳ refresh button - src/comfydv/__init__.py: register all 14 Ollama nodes in NODE_CLASS_MAPPINGS and NODE_DISPLAY_NAME_MAPPINGS Closes T011, T012, T013, T014. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * test(006): full Ollama test suite — T015-T031 all US TDD-T commits (T-T032) Add tests/test_ollama.py with 6 test classes covering all 14 nodes: - TestUS1OllamaConnection: client type, custom host, unreachable error - TestUS2ModelSelection: fetch list, selector output, COMBO type, empty fallback - TestUS3ModelLifecycle: COMBO type, empty-model guard, load/unload integration - TestUS4ChatCompletion: COMBO type, single-turn, multi-turn, history growth - TestUS5ComposableOptions: all 7 option nodes + chaining + deterministic - TestUS6HistoryInspection: debug output, length counts - TestNodeContracts: parametrised over all 14 nodes for ComfyUI contract Also remove tests/__init__.py — its presence caused pytest to walk up past the repo root and add the parent directory to sys.path, making the root-level __init__.py win the 'comfydv' namespace over src/comfydv/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * style: ruff format test_ollama.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * feat(006): T032/T034 — extend packaging tests and manager entry for 14 Ollama nodes Update EXPECTED_NODENAMES in test_packaging.py to include all 14 Ollama node display names. Update comfy-manager-entry.json nodename array and description to register all 14 nodes with ComfyUI Manager. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * docs(006): T033 — add Ollama section to README and docs/index.md Add all 14 Ollama nodes to the node reference table and add a dedicated Ollama section covering minimal workflow, option nodes, and multi-turn conversation patterns. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * test(006): T035-T/I — TestLoggingConsistency: no print(), correct log levels Add TestLoggingConsistency class to tests/test_logging.py: - AST scan: assert zero print() calls across all src/comfydv/*.py - circuit_breaker: ComfyUI-absent branch uses logger.warning() not debug - random_choice: exception path calls logger.error() and uses plain raise - ollama: absent-ComfyUI paths emit at least 2 logger.warning() calls All checks pass (fixes were already in Phase 2 broken windows commit). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * docs: add Quickstart section to README — satisfies beacon doctor readme-completeness Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * chore(006): mark all 55 tasks complete in tasks.md — spec fully implemented Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb * docs(006): T036/T037 — add Ollama screenshots and docker extra_hosts for host Ollama routing Captures 4 Ollama node screenshots (client, chat, workflow, options) via Playwright against the live dev ComfyUI instance. Adds extra_hosts to docker-compose.dev.yml so the container can reach host-machine Ollama. Updates README and docs/index.md to embed all 8 screenshots. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
218 lines
7.2 KiB
Python
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].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",
|
|
"Ollama Model Selector",
|
|
"Ollama Load Model",
|
|
"Ollama Unload Model",
|
|
"Ollama 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_311_base(self):
|
|
text = DOCKERFILE.read_text()
|
|
assert "FROM python:3.11" in text, (
|
|
"Dockerfile must use python:3.11 base image (CPU-only, no CUDA)"
|
|
)
|