Files
Azornes-Comfyui-Model-Resolver/tests/test_downloader_boundaries.py
T

244 lines
10 KiB
Python

import os
import tempfile
import threading
import unittest
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from core.download.api import context as downloader
from core.download.aria2_backend import recover_aria2_missing_control_file
from core.download.orchestrator import find_active_download_for_path
from core.path_utils import normalize_metadata_file_path
class DownloaderBoundaryTests(unittest.TestCase):
def test_build_download_headers_cleans_values_and_adds_civitai_defaults(self):
headers = downloader.build_download_headers(
"https://civitai.com/api/download/models/123",
{
" user-agent ": "custom\r\n-agent",
"X-Test": " value\n",
"Empty": " ",
},
)
self.assertEqual("custom-agent", headers["user-agent"])
self.assertEqual("value", headers["X-Test"])
self.assertNotIn("Empty", headers)
self.assertEqual("*/*", headers["Accept"])
self.assertEqual("identity", headers["Accept-Encoding"])
self.assertEqual("https://civitai.com/", headers["Referer"])
self.assertEqual("https://civitai.com", headers["Origin"])
def test_build_download_headers_does_not_add_provider_headers_for_other_hosts(self):
headers = downloader.build_download_headers("https://example.com/model.safetensors")
self.assertEqual(downloader.DOWNLOAD_USER_AGENT, headers["User-Agent"])
self.assertNotIn("Referer", headers)
self.assertNotIn("Origin", headers)
def test_json_safe_metadata_redacts_secrets_and_signed_query_parameters(self):
payload = downloader._json_safe_metadata(
{
"token": "secret-token",
"nested": {
"Authorization": "secret-header",
"url": "https://example.com/model?token=secret&keep=yes",
},
"items": ({"cookie": "secret-cookie"}, {1, 2}),
"custom": SimpleNamespace(value="safe"),
}
)
self.assertNotIn("token", payload)
self.assertNotIn("Authorization", payload["nested"])
self.assertEqual("https://example.com/model?keep=yes", payload["nested"]["url"])
self.assertEqual({}, payload["items"][0])
self.assertEqual({1, 2}, set(payload["items"][1]))
self.assertEqual("namespace(value='safe')", payload["custom"])
def test_metadata_helper_coercions_preserve_non_numeric_values(self):
self.assertIsNone(downloader._coerce_int_or_value(""))
self.assertEqual(12, downloader._coerce_int_or_value("12"))
self.assertEqual("unknown", downloader._coerce_int_or_value("unknown"))
self.assertEqual(12, downloader._coerce_size("12.9"))
self.assertEqual(0, downloader._coerce_size("not-a-size"))
self.assertEqual("models/subfolder", downloader.normalize_metadata_file_path("models\\subfolder"))
self.assertEqual("models/subfolder", normalize_metadata_file_path("models\\subfolder"))
def test_metadata_source_and_model_type_helpers_cover_provider_variants(self):
self.assertEqual(
"diffusion_model",
downloader._resolve_lora_manager_model_type("unknown", "diffusion model"),
)
self.assertEqual(
"embedding",
downloader._resolve_lora_manager_model_type("unknown", "textual inversion"),
)
self.assertEqual("civitai_api", downloader._metadata_source_value("civitai"))
self.assertEqual("existing", downloader._metadata_source_value("unknown", "existing"))
self.assertIsNone(downloader._metadata_source_value("unknown"))
def test_aria2_rpc_sends_authenticated_json_rpc_request(self):
response = MagicMock(status_code=200, text='{"result": {"version": "1.36"}}')
response.json.return_value = {"result": {"version": "1.36"}}
with patch.object(downloader, "aria2_rpc_url", "http://127.0.0.1:6800/jsonrpc"), patch.object(
downloader, "aria2_rpc_secret", "rpc-secret"
), patch(
"core.download.api.context.requests.post",
return_value=response,
) as mock_post:
result = downloader._aria2_rpc("aria2.getVersion")
self.assertEqual({"version": "1.36"}, result)
request_payload = mock_post.call_args.kwargs["json"]
self.assertEqual("aria2.getVersion", request_payload["method"])
self.assertEqual("token:rpc-secret", request_payload["params"][0])
def test_aria2_rpc_rejects_missing_endpoint_and_invalid_json(self):
with patch.object(downloader, "aria2_rpc_url", ""), self.assertRaises(
downloader.Aria2Error
):
downloader._aria2_rpc("aria2.getVersion")
response = MagicMock(status_code=200, text="not-json")
response.json.side_effect = ValueError("invalid json")
with patch.object(
downloader, "aria2_rpc_url", "http://127.0.0.1:6800/jsonrpc"
), patch(
"core.download.api.context.requests.post",
return_value=response,
), self.assertRaisesRegex(
downloader.Aria2Error, "non-JSON"
):
downloader._aria2_rpc("aria2.getVersion")
def test_aria2_status_helpers_parse_values_and_paths(self):
self.assertEqual(12, downloader._parse_aria2_int("12"))
self.assertEqual(0, downloader._parse_aria2_int("not-a-number"))
self.assertEqual(
"C:/models/actual.safetensors",
downloader._resolve_aria2_completed_path(
{"files": [{"path": "C:/models/actual.safetensors"}]},
"fallback.safetensors",
),
)
self.assertEqual(
"fallback.safetensors",
downloader._resolve_aria2_completed_path({}, "fallback.safetensors"),
)
def test_aria2_action_error_helper_recognizes_idempotent_states(self):
self.assertTrue(downloader._aria2_action_error_is_ok("paused", "already paused"))
self.assertTrue(downloader._aria2_action_error_is_ok("downloading", "not paused"))
self.assertFalse(downloader._aria2_action_error_is_ok("paused", "permission denied"))
self.assertFalse(downloader._aria2_action_error_is_ok("completed", "already paused"))
def test_aria2_missing_control_file_error_is_detected(self):
self.assertTrue(
downloader._is_aria2_missing_control_file_error(
"File exists, but a control file(*.aria2) does not exist. "
"Download was canceled; add --allow-overwrite=true."
)
)
self.assertFalse(
downloader._is_aria2_missing_control_file_error(
"TLS handshake failed"
)
)
def test_aria2_missing_control_file_recovery_restarts_only_when_alone(self):
facade = SimpleNamespace(
aria2_lock=threading.Lock(),
aria2_transfers={"download-1": {"gid": "gid-1"}},
_aria2_rpc=MagicMock(),
stop_aria2_daemon=MagicMock(return_value={"success": True}),
_ensure_aria2_daemon=MagicMock(),
)
self.assertTrue(
recover_aria2_missing_control_file(
"download-1",
"gid-1",
{"download_backend": "aria2"},
dependencies=facade,
)
)
facade.stop_aria2_daemon.assert_called_once_with(
reason="stale-control-file"
)
facade._ensure_aria2_daemon.assert_called_once()
self.assertNotIn("download-1", facade.aria2_transfers)
facade.aria2_transfers.update(
{
"download-1": {"gid": "gid-1"},
"download-2": {"gid": "gid-2"},
}
)
facade.stop_aria2_daemon.reset_mock()
self.assertFalse(
recover_aria2_missing_control_file(
"download-1",
"gid-1",
dependencies=facade,
)
)
facade.stop_aria2_daemon.assert_not_called()
def test_active_download_path_is_reused_case_insensitively(self):
active_id = find_active_download_for_path(
{
"active": {
"status": "downloading",
"path": r"C:\Models\Qwen.safetensors",
},
"finished": {
"status": "completed",
"path": r"C:\Models\Other.safetensors",
},
},
r"c:\models\qwen.safetensors",
os,
)
self.assertEqual("active", active_id)
def test_progress_snapshot_is_copied_and_completed_entries_can_be_cleared(self):
with patch.dict(
downloader.download_progress,
{
"active": {"status": "downloading", "progress": 20},
"completed": {"status": "completed"},
"failed": {"status": "error"},
"cancelled": {"status": "cancelled"},
},
clear=True,
), patch.object(
downloader, "cancelled_downloads", {"cancelled", "active"}
):
snapshot = downloader.get_all_progress()
snapshot["active"]["progress"] = 100
downloader.clear_completed_downloads()
self.assertEqual(20, downloader.download_progress["active"]["progress"])
self.assertEqual({"active"}, set(downloader.download_progress))
self.assertEqual({"active"}, downloader.cancelled_downloads)
def test_delete_python_partial_download_file_removes_only_partial_path(self):
with tempfile.TemporaryDirectory() as temp_dir:
partial_path = os.path.join(temp_dir, "model.safetensors.partial")
final_path = os.path.join(temp_dir, "model.safetensors")
open(partial_path, "wb").close()
open(final_path, "wb").close()
downloader._delete_python_partial_download_file(partial_path)
self.assertFalse(os.path.exists(partial_path))
self.assertTrue(os.path.exists(final_path))
if __name__ == "__main__":
unittest.main()