diff --git a/scripts/consistency_check.py b/scripts/consistency_check.py index 2977d69..d60f9c7 100644 --- a/scripts/consistency_check.py +++ b/scripts/consistency_check.py @@ -483,9 +483,15 @@ def _inject_task_id_frontmatter_list( # API helpers # --------------------------------------------------------------------------- -def _api_get(api_base: str, path: str, params: dict | None = None) -> Any: +def _api_get( + api_base: str, + path: str, + params: dict | None = None, + *, + return_error: bool = False, +) -> Any: if not _HAS_HTTPX: - return None + return {"_error": "httpx is not installed"} if return_error else None # Only append trailing slash to the path component, not to query strings if "?" not in path and not path.endswith("/"): path += "/" @@ -495,7 +501,15 @@ def _api_get(api_base: str, path: str, params: dict | None = None) -> Any: r = c.get(path, params=filtered if filtered else None) r.raise_for_status() return r.json() - except Exception: + except _httpx.HTTPStatusError as exc: + if exc.response.status_code == 404: + return None + if return_error: + return {"_error": str(exc)} + return None + except Exception as exc: + if return_error: + return {"_error": str(exc)} return None @@ -629,7 +643,17 @@ def _infer_slug_from_path(api_base: str, path: str) -> "tuple[str, str] | None": def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = None) -> ConsistencyReport: """Run all consistency checks for a registered repo.""" - repo = _api_get(api_base, f"/repos/{repo_slug}") + repo = _api_get(api_base, f"/repos/{repo_slug}", return_error=True) + if isinstance(repo, dict) and "_error" in repo: + report = ConsistencyReport(repo_slug=repo_slug, repo_path=repo_path_override or "(unknown)") + report.add( + severity="FAIL", check_id="C-00", + message=( + f"Could not query State Hub API at {api_base}: {repo['_error']}" + ), + fixable=False, + ) + return report if repo is None: report = ConsistencyReport(repo_slug=repo_slug, repo_path="(unknown)") report.add( @@ -1528,6 +1552,8 @@ def fix_repo( ) -> ConsistencyReport: """Run checks then apply all auto-fixable issues. Returns updated report.""" report = check_repo(api_base, repo_slug, repo_path_override) + if any(i.check_id == "C-00" for i in report.failures): + return report # Auto-register this machine's path in host_paths so future runs work # without --repo-path. Idempotent: skipped when already correct. diff --git a/tests/test_consistency_check.py b/tests/test_consistency_check.py index 41f458f..c452698 100644 --- a/tests/test_consistency_check.py +++ b/tests/test_consistency_check.py @@ -379,6 +379,43 @@ class TestRenderText: assert "C-08" not in text +class TestRepoLookupFailures: + def test_check_repo_reports_api_error_distinct_from_missing_repo(self, monkeypatch): + def fake_get(_api_base, _path, params=None, *, return_error=False): + assert params is None + return {"_error": "Connection refused"} if return_error else None + + monkeypatch.setattr("consistency_check._api_get", fake_get) + + report = check_repo("http://127.0.0.1:8000", "state-hub") + + assert report.repo_path == "(unknown)" + assert len(report.failures) == 1 + assert report.failures[0].check_id == "C-00" + assert "Could not query State Hub API" in report.failures[0].message + assert "not found in state-hub DB" not in report.failures[0].message + + def test_fix_repo_does_not_push_when_repo_lookup_failed(self, monkeypatch): + pushed_paths = [] + + def fake_get(_api_base, _path, params=None, *, return_error=False): + assert params is None + return {"_error": "Connection refused"} if return_error else None + + def fake_push(repo_path): + pushed_paths.append(repo_path) + return True, "pushed" + + monkeypatch.setattr("consistency_check._api_get", fake_get) + monkeypatch.setattr("consistency_check._git_push", fake_push) + + report = fix_repo("http://127.0.0.1:8000", "state-hub") + + assert report.failures[0].check_id == "C-00" + assert pushed_paths == [] + assert report.fixes_applied == [] + + class TestRenormalizationGuide: def test_registry_contains_c23_rule(self): rule = next(rule for rule in RENORMALIZATION_RULES if rule.check_id == "C-23") @@ -809,7 +846,7 @@ class TestLifecycleRenormalization: "description": None, } - def fake_get(_api_base, path, params=None): + def fake_get(_api_base, path, params=None, **_kwargs): if path == "/repos/state-hub": import socket