Files
ausboss-ComfyUI-AusBoss/tests/test_registry_status.py

166 lines
8.1 KiB
Python

"""A green upload must never stand in for the target version's approval."""
import json
import contextlib
import io
import os
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import patch
import urllib.error
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
from registry_status import version_status
import registry_status
class RegistryStatusTests(unittest.TestCase):
def entry(self, version, status, **extra):
return {"node_id": "ausboss-nodes", "version": version, "status": status, **extra}
def test_older_active_version_does_not_clear_target(self):
versions = [self.entry("1.3.0", "NodeVersionStatusActive"),
self.entry("2.0.0", "NodeVersionStatusFlagged")]
self.assertEqual(version_status(versions, "2.0.0")[0], "NodeVersionStatusFlagged")
def test_absent_target_is_not_published(self):
self.assertEqual(version_status([], "2.0.0")[0], "NotPublished")
def test_current_decision_wins_over_history(self):
reason = json.dumps({"message": "Needs a fix", "statusHistory": [{"status": "NodeVersionStatusActive"}]})
result = version_status([self.entry("2.0.0", "NodeVersionStatusBanned", status_reason=reason)], "2.0.0")
self.assertEqual(result, ("NodeVersionStatusBanned", "Needs a fix"))
def test_exact_active_target(self):
self.assertEqual(version_status([self.entry("2.0.0", "NodeVersionStatusActive")], "2.0.0"),
("NodeVersionStatusActive", ""))
def test_wrong_pack_and_unknown_status_are_errors(self):
for entry in [self.entry("2.0.0", "FutureStatus"),
self.entry("2.0.0", "NodeVersionStatusActive", node_id="different-pack")]:
with self.subTest(entry=entry), self.assertRaises(ValueError):
version_status([entry], "2.0.0")
def test_malformed_and_duplicate_responses_are_errors(self):
entry = self.entry("2.0.0", "NodeVersionStatusActive")
for payload in [{"status": "NodeStatusActive"}, [None], [entry, entry]]:
with self.subTest(payload=payload), self.assertRaises(ValueError):
version_status(payload, "2.0.0")
class RegistryReportTests(unittest.TestCase):
def run_report(self, status, report=True, reason=""):
with tempfile.TemporaryDirectory() as tmp:
summary, output = Path(tmp) / "summary.md", Path(tmp) / "output"
log = io.StringIO()
with (
patch.dict(os.environ, {"GITHUB_ACTIONS": "true", "GITHUB_STEP_SUMMARY": str(summary),
"GITHUB_OUTPUT": str(output)}, clear=True),
patch.object(registry_status, "fetch_version_status", return_value=(status, reason)),
contextlib.redirect_stdout(log),
):
code = registry_status.main(["--version", "2.0.2"] + (["--report"] if report else []))
return code, summary.read_text(), output.read_text(), log.getvalue()
def test_waiting_review_is_a_successful_report_but_never_approval(self):
for status in registry_status.REVIEW_STATUSES:
with self.subTest(status=status):
code, summary, output, log = self.run_report(status)
self.assertEqual(code, 0)
self.assertIn("**Approved: no.**", summary)
self.assertIn("approved=false", output)
self.assertIn("do not republish", log.lower())
self.assertNotIn("::error", log)
self.assertEqual(self.run_report(status, report=False)[0], 2)
def test_active_is_the_only_approved_result(self):
for report in (True, False):
code, summary, output, _ = self.run_report("NodeVersionStatusActive", report=report)
self.assertEqual(code, 0)
self.assertIn("**Approved: yes.**", summary)
self.assertIn("approved=true", output)
def test_bans_deletions_and_missing_versions_still_fail(self):
for status in ("NodeVersionStatusBanned", "NodeVersionStatusDeleted", "NotPublished"):
with self.subTest(status=status):
code, _, output, log = self.run_report(status)
self.assertEqual(code, 2)
self.assertIn("approved=false", output)
self.assertIn("::error", log)
def test_remote_reason_cannot_inject_a_workflow_command(self):
_, _, _, log = self.run_report("NodeVersionStatusFlagged", reason="review\n::error::injected")
self.assertIn("Reason: ::error::injected", log)
self.assertNotIn("\n::error::injected", log)
def test_failed_lookup_remains_an_error(self):
with (
patch.dict(os.environ, {}, clear=True),
patch.object(registry_status, "fetch_version_status", side_effect=urllib.error.URLError("unavailable")),
contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()),
):
self.assertEqual(registry_status.main(["--report", "--version", "2.0.2"]), 1)
class RegistryRetryTests(unittest.TestCase):
def response(self, versions):
return io.BytesIO(json.dumps(versions).encode())
def entry(self):
return {"node_id": "ausboss-nodes", "version": "2.0.2", "status": "NodeVersionStatusPending"}
def test_post_upload_visibility_delay_recovers(self):
with (
patch.object(registry_status.urllib.request, "urlopen", side_effect=[self.response([]), self.response([self.entry()])]) as get,
patch.object(registry_status.time, "sleep") as sleep,
):
self.assertEqual(registry_status.fetch_version_status("2.0.2")[0], "NodeVersionStatusPending")
self.assertEqual(get.call_count, 2)
sleep.assert_called_once_with(5)
def test_transient_failure_recovers_and_does_not_wait_for_approval(self):
for error in (urllib.error.URLError("offline"), TimeoutError(),
urllib.error.HTTPError(registry_status.STATUS_URL, 503, "unavailable", {}, None),
urllib.error.HTTPError(registry_status.STATUS_URL, 429, "busy", {}, None)):
with (
self.subTest(error=error),
patch.object(registry_status.urllib.request, "urlopen", side_effect=[error, self.response([self.entry()])]) as get,
patch.object(registry_status.time, "sleep"),
):
self.assertEqual(registry_status.fetch_version_status("2.0.2")[0], "NodeVersionStatusPending")
self.assertEqual(get.call_count, 2)
def test_missing_version_and_outage_have_bounded_retries(self):
for responses in ([self.response([]) for _ in range(3)], urllib.error.URLError("offline")):
with (
self.subTest(responses=responses),
patch.object(registry_status.urllib.request, "urlopen", side_effect=responses) as get,
patch.object(registry_status.time, "sleep") as sleep,
):
if isinstance(responses, list):
self.assertEqual(registry_status.fetch_version_status("2.0.2")[0], "NotPublished")
else:
with self.assertRaises(urllib.error.URLError):
registry_status.fetch_version_status("2.0.2")
self.assertEqual(get.call_count, 3)
self.assertEqual(sleep.call_count, 2)
def test_permanent_http_and_malformed_data_fail_without_retries(self):
for response, error in ((urllib.error.HTTPError(registry_status.STATUS_URL, 403, "forbidden", {}, None), urllib.error.HTTPError),
(io.BytesIO(b"not json"), ValueError)):
with (
self.subTest(error=error),
patch.object(registry_status.urllib.request, "urlopen", side_effect=[response]) as get,
patch.object(registry_status.time, "sleep") as sleep,
):
with self.assertRaises(error):
registry_status.fetch_version_status("2.0.2")
self.assertEqual(get.call_count, 1)
sleep.assert_not_called()
if __name__ == "__main__":
unittest.main()