Skip to content

Commit b105302

Browse files
committed
Refactor file_utils and add comprehensive tests 🧪
- Simplify type hints in file_utils.py by replacing List with built-in list - Update file reading logic in _load_comfyignore_spec for clarity - Introduce new test suite for file_utils, covering various scenarios - Enhance requirements.compiled with additional comments for clarity
1 parent f1f65a0 commit b105302

File tree

3 files changed

+156
-5
lines changed

3 files changed

+156
-5
lines changed

‎comfy_cli/file_utils.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pathlib
44
import subprocess
55
import zipfile
6-
from typing import List, Optional, Union
6+
from typing import Optional, Union
77

88
import httpx
99
import requests
@@ -92,7 +92,7 @@ def _load_comfyignore_spec(ignore_filename: str = ".comfyignore") -> Optional[Pa
9292
if not os.path.exists(ignore_filename):
9393
return None
9494
try:
95-
with open(ignore_filename, "r", encoding="utf-8") as ignore_file:
95+
with open(ignore_filename, encoding="utf-8") as ignore_file:
9696
patterns = [
9797
line.strip()
9898
for line in ignore_file
@@ -107,7 +107,7 @@ def _load_comfyignore_spec(ignore_filename: str = ".comfyignore") -> Optional[Pa
107107
return PathSpec.from_lines("gitwildmatch", patterns)
108108

109109

110-
def list_git_tracked_files(base_path: Union[str, os.PathLike] = ".") -> List[str]:
110+
def list_git_tracked_files(base_path: Union[str, os.PathLike] = ".") -> list[str]:
111111
try:
112112
result = subprocess.check_output(
113113
["git", "-C", os.fspath(base_path), "ls-files"],
@@ -126,7 +126,7 @@ def _normalize_path(path: str) -> str:
126126
return rel_path.replace("\\", "/")
127127

128128

129-
def _is_force_included(rel_path: str, include_prefixes: List[str]) -> bool:
129+
def _is_force_included(rel_path: str, include_prefixes: list[str]) -> bool:
130130
return any(
131131
rel_path == prefix or rel_path.startswith(prefix + "/")
132132
for prefix in include_prefixes
@@ -137,7 +137,7 @@ def _is_force_included(rel_path: str, include_prefixes: List[str]) -> bool:
137137
def zip_files(zip_filename, includes=None):
138138
"""Zip git-tracked files respecting optional .comfyignore patterns."""
139139
includes = includes or []
140-
include_prefixes: List[str] = [
140+
include_prefixes: list[str] = [
141141
_normalize_path(os.path.normpath(include.lstrip("/")))
142142
for include in includes
143143
]
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import json
2+
import pathlib
3+
from unittest.mock import Mock, patch
4+
5+
import pytest
6+
import requests
7+
8+
from comfy_cli.file_utils import (
9+
DownloadException,
10+
check_unauthorized,
11+
download_file,
12+
extract_package_as_zip,
13+
guess_status_code_reason,
14+
upload_file_to_signed_url,
15+
)
16+
17+
18+
def test_guess_status_code_reason_401_with_json():
19+
message = json.dumps({"message": "API token required"}).encode()
20+
result = guess_status_code_reason(401, message)
21+
assert "API token required" in result
22+
assert "Unauthorized download (401)" in result
23+
24+
25+
def test_guess_status_code_reason_401_without_json():
26+
result = guess_status_code_reason(401, "not json")
27+
assert "Unauthorized download (401)" in result
28+
assert "manually log into a browser" in result
29+
30+
31+
def test_guess_status_code_reason_403():
32+
result = guess_status_code_reason(403, "")
33+
assert "Forbidden url (403)" in result
34+
35+
36+
def test_guess_status_code_reason_404():
37+
result = guess_status_code_reason(404, "")
38+
assert "not found on server (404)" in result
39+
40+
41+
def test_guess_status_code_reason_unknown():
42+
result = guess_status_code_reason(500, "")
43+
assert "Unknown error occurred (status code: 500)" in result
44+
45+
46+
@patch("requests.get")
47+
def test_check_unauthorized_true(mock_get):
48+
mock_response = Mock()
49+
mock_response.status_code = 401
50+
mock_get.return_value = mock_response
51+
52+
assert check_unauthorized("http://example.com") is True
53+
54+
55+
@patch("requests.get")
56+
def test_check_unauthorized_false(mock_get):
57+
mock_response = Mock()
58+
mock_response.status_code = 200
59+
mock_get.return_value = mock_response
60+
61+
assert check_unauthorized("http://example.com") is False
62+
63+
64+
@patch("requests.get")
65+
def test_check_unauthorized_exception(mock_get):
66+
mock_get.side_effect = requests.RequestException()
67+
68+
assert check_unauthorized("http://example.com") is False
69+
70+
71+
@patch("httpx.stream")
72+
def test_download_file_success(mock_stream, tmp_path):
73+
mock_response = Mock()
74+
mock_response.status_code = 200
75+
mock_response.headers = {"Content-Length": "1024"}
76+
mock_response.iter_bytes.return_value = [b"test data"]
77+
mock_response.__enter__ = Mock(return_value=mock_response)
78+
mock_response.__exit__ = Mock(return_value=None)
79+
mock_stream.return_value = mock_response
80+
81+
test_file = tmp_path / "test.txt"
82+
download_file("http://example.com", test_file)
83+
84+
assert test_file.exists()
85+
assert test_file.read_bytes() == b"test data"
86+
87+
88+
@patch("httpx.stream")
89+
def test_download_file_failure(mock_stream):
90+
mock_response = Mock()
91+
mock_response.status_code = 404
92+
mock_response.read.return_value = ""
93+
mock_response.__enter__ = Mock(return_value=mock_response)
94+
mock_response.__exit__ = Mock(return_value=None)
95+
mock_stream.return_value = mock_response
96+
97+
with pytest.raises(DownloadException) as exc_info:
98+
download_file("http://example.com", pathlib.Path("test.txt"))
99+
100+
assert "Failed to download file" in str(exc_info.value)
101+
102+
103+
@patch("requests.put")
104+
def test_upload_file_success(mock_put, tmp_path):
105+
test_file = tmp_path / "test.zip"
106+
test_file.write_bytes(b"test data")
107+
108+
mock_response = Mock()
109+
mock_response.status_code = 200
110+
mock_put.return_value = mock_response
111+
112+
upload_file_to_signed_url("http://example.com", str(test_file))
113+
114+
mock_put.assert_called_once()
115+
116+
117+
@patch("requests.put")
118+
def test_upload_file_failure(mock_put, tmp_path):
119+
test_file = tmp_path / "test.zip"
120+
test_file.write_bytes(b"test data")
121+
122+
mock_response = Mock()
123+
mock_response.status_code = 500
124+
mock_response.text = "Server error"
125+
mock_put.return_value = mock_response
126+
127+
with pytest.raises(Exception) as exc_info:
128+
upload_file_to_signed_url("http://example.com", str(test_file))
129+
130+
assert "Upload failed" in str(exc_info.value)
131+
132+
133+
def test_extract_package_as_zip(tmp_path):
134+
# Create a test zip file
135+
import zipfile
136+
137+
zip_path = tmp_path / "test.zip"
138+
extract_path = tmp_path / "extracted"
139+
140+
with zipfile.ZipFile(zip_path, "w") as test_zip:
141+
test_zip.writestr("test.txt", "test content")
142+
143+
extract_package_as_zip(zip_path, extract_path)
144+
145+
assert (extract_path / "test.txt").exists()
146+
assert (extract_path / "test.txt").read_text() == "test content"

‎tests/uv/mock_requirements/requirements.compiled‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
--index-url https://pypi.org/simple
44
--extra-index-url https://download.pytorch.org/whl/rocm6.1
55

6+
colorama==0.4.6
7+
# via
8+
# --override override.txt
9+
# tqdm
10+
# from https://download.pytorch.org/whl/rocm6.1
611
mpmath==1.3.0
712
# via
813
# -r /home/tel/git/comfy-cli/tests/uv/mock_requirements/y_reqs.txt

0 commit comments

Comments
 (0)