Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions comfy_cli/command/custom_nodes/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ def install(
),
):
if "all" in nodes:
typer.echo(f"Invalid command: {mode}. `install all` is not allowed", err=True)
typer.echo("`install all` is not allowed", err=True)
raise typer.Exit(code=1)

exclusive_flags = [
Expand Down Expand Up @@ -670,7 +670,7 @@ def reinstall(
),
):
if "all" in nodes:
typer.echo(f"Invalid command: {mode}. `reinstall all` is not allowed", err=True)
typer.echo("`reinstall all` is not allowed", err=True)
raise typer.Exit(code=1)

exclusive_flags = [name for name, val in [("--fast-deps", fast_deps), ("--uv-compile", uv_compile)] if val]
Expand Down Expand Up @@ -715,7 +715,7 @@ def uninstall(
),
):
if "all" in nodes:
typer.echo(f"Invalid command: {mode}. `uninstall all` is not allowed", err=True)
typer.echo("`uninstall all` is not allowed", err=True)
raise typer.Exit(code=1)

validate_mode(mode)
Expand Down Expand Up @@ -1155,7 +1155,7 @@ def registry_install(
node_specific_path = custom_nodes_path / node_id # Subdirectory for the node
if node_specific_path.exists():
print(
f"[bold red] The node {node_id} already exists in the workspace. This migit delete any model files in the node.[/bold red]"
f"[bold red] The node {node_id} already exists in the workspace. This might delete any model files in the node.[/bold red]"
)

confirm = ui.prompt_confirm_action(
Expand Down
2 changes: 1 addition & 1 deletion comfy_cli/command/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ def handle_github_rate_limit(response):
remaining = int(response.headers.get("x-ratelimit-remaining", 0))
if remaining == 0:
reset_time = int(response.headers.get("x-ratelimit-reset", 0))
message = f"Primary rate limit from Github exceeded! Please retry after: {reset_time})"
message = f"Primary rate limit from Github exceeded! Please retry after: {reset_time}"
raise GitHubRateLimitError(message)

if "retry-after" in response.headers:
Expand Down
6 changes: 3 additions & 3 deletions comfy_cli/command/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def launch_comfyui(extra, frontend_pr=None, python=sys.executable):

if reboot_path is None:
print("[bold red]ComfyUI is not installed.[/bold red]\n")
exit(res)
exit(res.returncode)

if not os.path.exists(reboot_path):
exit(res.returncode)
Expand Down Expand Up @@ -135,10 +135,10 @@ def redirector_stdout():

if reboot_path is None:
print("[bold red]ComfyUI is not installed.[/bold red]\n")
os._exit(process.pid)
os._exit(1)

if not os.path.exists(reboot_path):
os._exit(process.pid)
os._exit(process.returncode)

os.remove(reboot_path)
except KeyboardInterrupt:
Expand Down
37 changes: 36 additions & 1 deletion tests/comfy_cli/command/github/test_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
from typer.testing import CliRunner

from comfy_cli.cmdline import app, g_exclusivity, g_gpu_exclusivity
from comfy_cli.command.install import PRInfo, fetch_pr_info, find_pr_by_branch, handle_pr_checkout, parse_pr_reference
from comfy_cli.command.install import (
GitHubRateLimitError,
PRInfo,
fetch_pr_info,
find_pr_by_branch,
handle_github_rate_limit,
handle_pr_checkout,
parse_pr_reference,
)
from comfy_cli.git_utils import checkout_pr


Expand Down Expand Up @@ -437,5 +445,32 @@ def test_checkout_pr_remote_already_exists(self, mock_getcwd, mock_chdir, mock_s
assert mock_subprocess.call_count == 3


class TestHandleGithubRateLimit:
def test_primary_rate_limit_message_format(self):
"""Verify the error message does not contain stray characters."""
mock_response = Mock()
mock_response.headers = {"x-ratelimit-remaining": "0", "x-ratelimit-reset": "1700000000"}

with pytest.raises(GitHubRateLimitError) as exc_info:
handle_github_rate_limit(mock_response)

msg = str(exc_info.value)
assert "1700000000" in msg
assert msg.endswith("1700000000") # no stray trailing characters

def test_retry_after_header(self):
mock_response = Mock()
mock_response.headers = {"x-ratelimit-remaining": "5", "retry-after": "30"}

with pytest.raises(GitHubRateLimitError, match="30 seconds"):
handle_github_rate_limit(mock_response)

def test_no_rate_limit_does_not_raise(self):
mock_response = Mock()
mock_response.headers = {"x-ratelimit-remaining": "100"}

handle_github_rate_limit(mock_response) # should not raise


if __name__ == "__main__":
pytest.main([__file__])
7 changes: 6 additions & 1 deletion tests/comfy_cli/command/nodes/test_node_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,15 @@ def test_fix_with_uv_compile():
def test_uninstall_rejects_all():
result = runner.invoke(app, ["uninstall", "all"])
assert result.exit_code != 0
assert "`uninstall all` is not allowed" in result.output
assert "Invalid command" not in result.output


def test_reinstall_rejects_all():
result = runner.invoke(app, ["reinstall", "all"])
assert result.exit_code != 0
assert "`reinstall all` is not allowed" in result.output
assert "Invalid command" not in result.output


def test_validate_mode_rejects_invalid():
Expand Down Expand Up @@ -310,7 +314,8 @@ def test_install_deps_with_workflow(tmp_path):
def test_install_rejects_all():
result = runner.invoke(app, ["install", "all"])
assert result.exit_code != 0
assert "not allowed" in result.output
assert "`install all` is not allowed" in result.output
assert "Invalid command" not in result.output


def test_simple_show_installed():
Expand Down
14 changes: 14 additions & 0 deletions tests/comfy_cli/test_launch_python_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ def test_uses_python_param(self):
assert cmd[0] == "/resolved/python"
assert cmd[0] != sys.executable

@pytest.mark.parametrize("returncode", [0, 1, 42])
def test_foreground_exit_code_matches_subprocess(self, returncode):
"""exit() should receive the subprocess returncode, not the CompletedProcess object."""
mock_result = subprocess.CompletedProcess(args=[], returncode=returncode)

with (
patch("comfy_cli.command.launch.ConfigManager"),
patch("comfy_cli.command.launch.subprocess.run", return_value=mock_result),
):
with pytest.raises(SystemExit) as exc_info:
launch.launch_comfyui(extra=[], python="/resolved/python")

assert exc_info.value.code == returncode


class TestLaunchResolvesWorkspacePython:
def test_resolves_and_passes_python(self):
Expand Down
Loading