from __future__ import annotations import subprocess from pathlib import Path import pytest from repo_manager.commands import registrar_reconcile as rr @pytest.fixture(autouse=True) def isolate_registrar_lock(tmp_path: Path, monkeypatch) -> None: """Keep unit tests independent from a live workstation registrar run.""" monkeypatch.setattr(rr, "LOCK_PATH", tmp_path / "registrar.lock") def _git(repo: Path, *args: str) -> None: subprocess.run(["git", *args], cwd=repo, check=True, capture_output=True) def _fixture(tmp_path: Path) -> Path: bare = tmp_path / "remote.git" repo = tmp_path / "demo" _git(tmp_path, "init", "--bare", str(bare)) _git(tmp_path, "clone", str(bare), str(repo)) _git(repo, "config", "user.name", "Test") _git(repo, "config", "user.email", "test@example.com") (repo / "workplans").mkdir() (repo / "workplans" / "DEMO-WP-0001.md").write_text( """--- id: DEMO-WP-0001 type: workplan title: Demo status: active --- ## Task ```task id: DEMO-WP-0001-T01 status: todo priority: high ``` """, encoding="utf-8", ) _git(repo, "add", ".") _git(repo, "commit", "-m", "seed") _git(repo, "push", "-u", "origin", "HEAD") return repo def test_requires_explicit_primary_confirmation(tmp_path: Path) -> None: repo = _fixture(tmp_path) result = rr.registrar_reconcile(repo) assert result.status == "rejected" assert result.error and result.error["code"] == "confirmation_required" def test_unrelated_historical_collision_does_not_block_scoped_request( tmp_path: Path, ) -> None: repo = _fixture(tmp_path) for number, task_uuid in ( (2, "22222222-2222-4222-8222-222222222222"), (3, "33333333-3333-4333-8333-333333333333"), ): (repo / "workplans" / f"DEMO-WP-000{number}.md").write_text( f"""--- id: DEMO-WP-000{number} type: workplan title: Historical {number} status: finished state_hub_workstream_id: "{number}{'1' * 7}-1111-4111-8111-111111111111" --- ## Historical task ```task id: DEMO-WP-9999-T01 status: done priority: low state_hub_task_id: "{task_uuid}" ``` """, encoding="utf-8", ) result = rr.registrar_reconcile(repo) assert result.status == "rejected" assert result.error and result.error["code"] == "confirmation_required" assert result.evidence["record_identity"]["identity_collisions"] assert result.evidence["blocking_identity_collisions"] == [] def test_requested_collision_still_fails_closed(tmp_path: Path) -> None: repo = _fixture(tmp_path) second = repo / "workplans" / "DEMO-WP-0002.md" second.write_text( """--- id: DEMO-WP-0002 type: workplan title: Conflicting request status: active --- ## Conflicting task ```task id: DEMO-WP-0001-T01 status: todo priority: high ``` """, encoding="utf-8", ) result = rr.registrar_reconcile(repo) assert result.status == "rejected" assert result.error and result.error["code"] == "record_identity_collision" assert result.evidence["blocking_identity_collisions"][0]["id"] == "DEMO-WP-0001-T01" def test_requested_invalid_identifier_fails_before_registration(tmp_path: Path) -> None: repo = _fixture(tmp_path) workplan = repo / "workplans" / "DEMO-WP-0001.md" workplan.write_text( workplan.read_text(encoding="utf-8").replace("DEMO-WP-0001", "DEMO-INVALID"), encoding="utf-8", ) result = rr.registrar_reconcile(repo) assert result.status == "rejected" assert result.error and result.error["code"] == "record_identifier_invalid" assert result.evidence["blocking_invalid_identifiers"][0]["id"] == "DEMO-INVALID" def test_unlinked_closed_workplan_is_registrar_work(tmp_path: Path) -> None: repo = _fixture(tmp_path) workplan = repo / "workplans" / "DEMO-WP-0001.md" workplan.write_text( workplan.read_text(encoding="utf-8").replace("status: active", "status: finished"), encoding="utf-8", ) assert rr._missing_identifiers(repo) == { "workplans": ["DEMO-WP-0001"], "tasks": ["DEMO-WP-0001-T01"], "intakes": [], "decisions": [], } def test_linked_closed_workplan_does_not_reopen_historical_task_gaps(tmp_path: Path) -> None: repo = _fixture(tmp_path) workplan = repo / "workplans" / "DEMO-WP-0001.md" text = workplan.read_text(encoding="utf-8") text = text.replace( "status: active\n---", 'status: finished\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) workplan.write_text(text, encoding="utf-8") assert rr._missing_identifiers(repo) == { "workplans": [], "tasks": [], "intakes": [], "decisions": [], } def test_rejects_dirty_or_unsynced_repository(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) monkeypatch.setattr(rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None)) (repo / "note.txt").write_text("dirty", encoding="utf-8") dirty = rr.registrar_reconcile(repo, confirm_primary=True) assert dirty.status == "rejected" assert dirty.error and dirty.error["code"] == "git_precondition_failed" assert "note.txt" in (dirty.error.get("message") or "") def test_generated_index_alone_does_not_fail_git_precondition(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) (repo / "WORK-RECORDS.md").write_text("# generated\n", encoding="utf-8") monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) def fake_run(command, *, env): workplan = repo / "workplans" / "DEMO-WP-0001.md" text = workplan.read_text(encoding="utf-8") text = text.replace( "status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) text = text.replace( "priority: high\n```", 'priority: high\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```', ) workplan.write_text(text, encoding="utf-8") return subprocess.CompletedProcess(command, 0, "ok", "") monkeypatch.setattr(rr, "_run_statehub", fake_run) result = rr.registrar_reconcile(repo, statehub_bin="statehub", confirm_primary=True) assert result.status == "applied" assert "WORK-RECORDS.md" in result.evidence.get("committed_paths", []) def test_incomplete_run_commits_writebacks_and_names_the_record( tmp_path: Path, monkeypatch ) -> None: repo = _fixture(tmp_path) monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) def fake_run(command, *, env): workplan = repo / "workplans" / "DEMO-WP-0001.md" text = workplan.read_text(encoding="utf-8") text = text.replace( "status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) workplan.write_text(text, encoding="utf-8") (repo / "WORK-RECORDS.md").write_text("# generated after mint\n", encoding="utf-8") return subprocess.CompletedProcess( command, 1, "! task DEMO-WP-0001-T01 not created: 500 Internal Server Error: Internal Server Error", "", ) monkeypatch.setattr(rr, "_run_statehub", fake_run) result = rr.registrar_reconcile(repo, statehub_bin="statehub", confirm_primary=True) assert result.status == "failed" assert result.error and result.error["code"] == "registration_incomplete" assert "DEMO-WP-0001-T01" in (result.error.get("message") or "") assert result.error.get("records") == ["DEMO-WP-0001-T01"] assert "not created:" in (result.error.get("message") or "") subject = subprocess.run( ["git", "log", "-1", "--format=%s"], cwd=repo, capture_output=True, text=True, check=True ).stdout.strip() assert subject == "chore(registrar): assign State Hub identifiers" workplan = (repo / "workplans" / "DEMO-WP-0001.md").read_text(encoding="utf-8") assert "11111111-1111-4111-8111-111111111111" in workplan def test_bootstrap_source_invalid_names_the_record(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) monkeypatch.setattr( rr, "_check_empty_repo_projection", lambda _api, _slug: ({"repo_id": "repo-1", "workplan_count": 0}, None), ) result = rr.registrar_reconcile( repo, statehub_bin="statehub", confirm_primary=True, bootstrap_empty_projection=True, ) assert result.status == "rejected" assert result.error and result.error["code"] == "bootstrap_source_invalid" assert "DEMO-WP-0001" in (result.error.get("message") or "") assert result.error.get("record") == "DEMO-WP-0001" def test_missing_scan_includes_intakes_and_decisions(tmp_path: Path) -> None: repo = _fixture(tmp_path) records = repo / "intakes" / "records.md" records.parent.mkdir() records.write_text( """# Records ```yaml id: DEMO-IN-0001 kind: intake title: Intake status: open ``` ```yaml id: DEMO-DEC-0001 kind: decision title: Decision status: open ``` """, encoding="utf-8", ) missing = rr._missing_identifiers(repo) assert missing["intakes"] == ["DEMO-IN-0001"] assert missing["decisions"] == ["DEMO-DEC-0001"] def test_missing_scan_includes_lowercase_top_level_record_files(tmp_path: Path) -> None: repo = _fixture(tmp_path) (repo / "intakes.md").write_text( """# Intakes ```yaml id: DEMO-IN-0002 kind: intake title: Standalone intake status: open ``` """, encoding="utf-8", ) (repo / "decisions.md").write_text( """# Decisions ```yaml id: DEMO-DEC-0002 kind: decision title: Standalone decision status: accepted ``` """, encoding="utf-8", ) missing = rr._missing_identifiers(repo) assert missing["intakes"] == ["DEMO-IN-0002"] assert missing["decisions"] == ["DEMO-DEC-0002"] def test_scopes_registrar_env_and_commits_assigned_ids(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) monkeypatch.setattr(rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None)) def fake_run(command, *, env): assert command[1:3] == ["fix-consistency", "--path"] assert env["STATEHUB_REGISTRAR"] == "1" workplan = repo / "workplans" / "DEMO-WP-0001.md" text = workplan.read_text(encoding="utf-8") text = text.replace("status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---') text = text.replace("priority: high\n```", 'priority: high\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```') workplan.write_text(text, encoding="utf-8") return subprocess.CompletedProcess(command, 0, "ok", "") monkeypatch.setattr(rr, "_run_statehub", fake_run) result = rr.registrar_reconcile( repo, statehub_bin="statehub", confirm_primary=True, ) assert result.status == "applied" assert result.evidence["missing_after"] == { "workplans": [], "tasks": [], "intakes": [], "decisions": [], } subject = subprocess.run( ["git", "log", "-1", "--format=%s"], cwd=repo, capture_output=True, text=True, check=True ).stdout.strip() assert subject == "chore(registrar): assign State Hub identifiers" def test_accepts_unrelated_assessment_fail_after_exact_requested_verification( tmp_path: Path, monkeypatch ) -> None: repo = _fixture(tmp_path) monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) def fake_run(command, *, env): workplan = repo / "workplans" / "DEMO-WP-0001.md" text = workplan.read_text(encoding="utf-8") text = text.replace( "status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) text = text.replace( "priority: high\n```", 'priority: high\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```', ) workplan.write_text(text, encoding="utf-8") return subprocess.CompletedProcess(command, 1, "unrelated C-03 remains", "") monkeypatch.setattr(rr, "_run_statehub", fake_run) monkeypatch.setattr( rr, "_verify_full_projection", lambda _api, workplans, tasks, records: ( { "expected_workplans": len(workplans), "expected_tasks": len(tasks), "expected_intakes": len(records["intake"]), "expected_decisions": len(records["decision"]), "missing_workplans": [], "missing_tasks": [], "missing_intakes": [], "missing_decisions": [], }, None, ), ) result = rr.registrar_reconcile( repo, statehub_bin="statehub", confirm_primary=True, ) assert result.status == "applied" assert result.evidence["requested_projection_verified"] is True assert result.evidence["requested_projection"]["expected_workplans"] == 1 assert result.evidence["requested_projection"]["expected_tasks"] == 1 def test_statehub_timeout_returns_structured_failure(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) monkeypatch.setattr( rr, "_run_statehub", lambda command, *, env: (_ for _ in ()).throw( subprocess.TimeoutExpired(command, rr.STATEHUB_TIMEOUT_SECONDS, output="partial") ), ) result = rr.registrar_reconcile( repo, statehub_bin="statehub", confirm_primary=True, ) assert result.status == "failed" assert result.error and result.error["code"] == "statehub_timeout" assert result.evidence["statehub_stdout_tail"] == "partial" assert result.evidence["missing_after"] == result.evidence["missing_before"] def test_repairs_an_already_identified_workplan_projection(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) workplan = repo / "workplans" / "DEMO-WP-0001.md" text = workplan.read_text(encoding="utf-8") text = text.replace( "status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) text = text.replace( "priority: high\n```", 'priority: high\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```', ) workplan.write_text(text, encoding="utf-8") brief = repo / ".custodian-brief.md" brief.write_text("authoritative local brief\n", encoding="utf-8") _git(repo, "add", ".") _git(repo, "commit", "-m", "add authoritative identifiers") _git(repo, "push") monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) monkeypatch.setattr( rr, "_projection_exists", lambda _api, projection_id: ({"id": projection_id}, None), ) def fake_run(command, *, env): assert env["STATEHUB_REGISTRAR"] == "1" brief.write_text("generated from partial projection\n", encoding="utf-8") return subprocess.CompletedProcess(command, 1, "legacy stale references remain", "") monkeypatch.setattr(rr, "_run_statehub", fake_run) result = rr.registrar_reconcile( repo, statehub_bin="statehub", confirm_primary=True, repair_workplan="DEMO-WP-0001", ) assert result.status == "applied" assert result.evidence["repair_projection_verified"] is True assert result.evidence["repair_projection_id"] == "11111111-1111-4111-8111-111111111111" assert result.evidence["restored_generated_paths"] == [".custodian-brief.md"] assert brief.read_text(encoding="utf-8") == "authoritative local brief\n" def test_repair_fails_when_exact_projection_remains_absent(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) workplan = repo / "workplans" / "DEMO-WP-0001.md" text = workplan.read_text(encoding="utf-8") text = text.replace( "status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) text = text.replace( "priority: high\n```", 'priority: high\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```', ) workplan.write_text(text, encoding="utf-8") _git(repo, "add", ".") _git(repo, "commit", "-m", "add authoritative identifiers") _git(repo, "push") monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) monkeypatch.setattr( rr, "_projection_exists", lambda _api, _projection_id: ({}, "workplan projection returned 404"), ) monkeypatch.setattr( rr, "_run_statehub", lambda command, *, env: subprocess.CompletedProcess( command, 1, "stale reference remains", "" ), ) result = rr.registrar_reconcile( repo, statehub_bin="statehub", confirm_primary=True, repair_workplan="DEMO-WP-0001", ) assert result.status == "failed" assert result.error and result.error["code"] == "registration_incomplete" assert result.evidence["repair_projection_verified"] is False assert "requested_projection_verified" not in result.evidence def test_bootstraps_and_verifies_a_completely_empty_projection(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) workplan = repo / "workplans" / "DEMO-WP-0001.md" text = workplan.read_text(encoding="utf-8") text = text.replace( "status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) text = text.replace( "priority: high\n```", 'priority: high\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```', ) workplan.write_text(text, encoding="utf-8") _git(repo, "add", ".") _git(repo, "commit", "-m", "add authoritative identifiers") _git(repo, "push") monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) monkeypatch.setattr( rr, "_check_empty_repo_projection", lambda _api, _slug: ({"repo_id": "repo-1", "workplan_count": 0}, None), ) monkeypatch.setattr( rr, "_verify_full_projection", lambda _api, workplans, tasks, records: ( { "expected_workplans": len(workplans), "expected_tasks": len(tasks), "expected_intakes": len(records["intake"]), "expected_decisions": len(records["decision"]), "missing_workplans": [], "missing_tasks": [], "missing_intakes": [], "missing_decisions": [], }, None, ), ) def fake_run(command, *, env): assert command[-1] == "--bootstrap-empty-projection" assert env["STATEHUB_REGISTRAR"] == "1" return subprocess.CompletedProcess(command, 2, "bootstrap warnings", "") monkeypatch.setattr(rr, "_run_statehub", fake_run) result = rr.registrar_reconcile( repo, statehub_bin="statehub", confirm_primary=True, bootstrap_empty_projection=True, ) assert result.status == "applied" assert result.evidence["bootstrap_source"] == { "workplans": 1, "tasks": 1, "intakes": 0, "decisions": 0, } assert result.evidence["bootstrap_projection_verified"] is True def test_empty_projection_bootstrap_refuses_existing_rows(tmp_path: Path, monkeypatch) -> None: repo = _fixture(tmp_path) monkeypatch.setattr( rr, "_check_primary", lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None), ) monkeypatch.setattr( rr, "_check_empty_repo_projection", lambda _api, _slug: ( {"repo_id": "repo-1", "workplan_count": 1}, "target repository projection is not empty", ), ) monkeypatch.setattr( rr, "_run_statehub", lambda *_args, **_kwargs: pytest.fail("non-empty bootstrap must not run State Hub"), ) result = rr.registrar_reconcile( repo, confirm_primary=True, bootstrap_empty_projection=True, ) assert result.status == "rejected" assert result.error and result.error["code"] == "bootstrap_precondition_failed" class TestCheckPrimary: """_check_primary must assert authority, not just liveness (CUST-WP-0067-T03). The previous version accepted any instance reporting status=ok and db=connected. A local cache and the central hub both satisfied that for seven weeks while every registration went to the cache. """ @staticmethod def _resp(payload): class R: def raise_for_status(self): return None def json(self): return payload return R() def test_accepts_declared_primary(self, monkeypatch): monkeypatch.setattr( rr.httpx, "get", lambda *a, **k: self._resp( {"status": "ok", "db": "connected", "instance_role": "primary"} ), ) _, err = rr._check_primary("http://hub") assert err is None def test_rejects_declared_cache(self, monkeypatch): monkeypatch.setattr( rr.httpx, "get", lambda *a, **k: self._resp( {"status": "ok", "db": "connected", "instance_role": "cache", "instance_label": "workstation"} ), ) _, err = rr._check_primary("http://hub") assert err and "cache" in err def test_rejects_healthy_instance_that_declares_nothing(self, monkeypatch): """A healthy hub is not thereby the authoritative one.""" monkeypatch.setattr( rr.httpx, "get", lambda *a, **k: self._resp({"status": "ok", "db": "connected"}), ) _, err = rr._check_primary("http://hub") assert err and "does not declare an instance role" in err def test_unverified_requires_an_explicit_opt_in(self, monkeypatch): monkeypatch.setattr( rr.httpx, "get", lambda *a, **k: self._resp({"status": "ok", "db": "connected"}), ) _, err = rr._check_primary("http://hub", allow_unverified=True) assert err is None class TestWorkplanBindings: """Registration must record the backing file in one pass (CUST-WP-0068-T07). `backing_filename` is what lets the read model tell a file-backed workplan from a hub-only orphan. It used to be written only by a later `fix-consistency` run, so freshly registered workplans stayed unbound and 35% of central's workplans recorded no backing file. """ @staticmethod def _repo(tmp_path): repo = tmp_path / "demo" (repo / "workplans" / "archived").mkdir(parents=True) (repo / "workplans" / "DEMO-WP-0001-a.md").write_text( '---\nid: DEMO-WP-0001\ntype: workplan\nstatus: active\n' 'state_hub_workstream_id: "11111111-1111-5111-8111-111111111111"\n---\n\n# a\n', encoding="utf-8", ) (repo / "workplans" / "archived" / "DEMO-WP-0002-b.md").write_text( '---\nid: DEMO-WP-0002\ntype: workplan\nstatus: finished\n' 'state_hub_workstream_id: "22222222-2222-5222-8222-222222222222"\n---\n\n# b\n', encoding="utf-8", ) (repo / "workplans" / "DEMO-WP-0003-unregistered.md").write_text( "---\nid: DEMO-WP-0003\ntype: workplan\nstatus: proposed\n---\n\n# c\n", encoding="utf-8", ) return repo def test_binds_every_identified_workplan(self, tmp_path, monkeypatch): repo = self._repo(tmp_path) sent = {} class R: def raise_for_status(self): return None def json(self): return {"updated": 2, "received": 2} def fake_put(url, json=None, timeout=None): sent["url"] = url sent["bindings"] = json["bindings"] return R() monkeypatch.setattr(rr.httpx, "put", fake_put) result = rr._sync_workplan_bindings(repo, "http://hub", "demo") assert result["ok"] and result["updated"] == 2 assert sent["url"].endswith("/workplans/index/bindings") by_id = {b["workplan_id"][:8]: b for b in sent["bindings"]} # The workplan with no identifier cannot be bound and must be skipped, # not sent with a null id. assert set(by_id) == {"11111111", "22222222"} assert by_id["11111111"]["relative_path"] == "workplans/DEMO-WP-0001-a.md" assert by_id["11111111"]["archived"] is False assert by_id["22222222"]["archived"] is True assert by_id["22222222"]["status"] == "finished" def test_non_canonical_status_is_omitted_not_guessed(self, tmp_path, monkeypatch): """The schema does not normalise, so a bad status 422s the whole batch.""" repo = self._repo(tmp_path) (repo / "workplans" / "DEMO-WP-0004-legacy.md").write_text( '---\nid: DEMO-WP-0004\ntype: workplan\nstatus: done\n' 'state_hub_workstream_id: "44444444-4444-5444-8444-444444444444"\n---\n\n# d\n', encoding="utf-8", ) sent = {} class R: def raise_for_status(self): return None def json(self): return {"updated": 3, "received": 3} monkeypatch.setattr( rr.httpx, "put", lambda url, json=None, timeout=None: (sent.update(b=json["bindings"]), R())[1], ) rr._sync_workplan_bindings(repo, "http://hub", "demo") legacy = next( b for b in sent["b"] if b["workplan_id"].startswith("44444444") ) assert legacy["status"] is None def test_binding_failure_never_fails_registration(self, tmp_path, monkeypatch): """Identifiers are already minted and committed; a bind can be retried.""" repo = self._repo(tmp_path) def boom(*a, **k): raise rr.httpx.HTTPError("hub unreachable") monkeypatch.setattr(rr.httpx, "put", boom) result = rr._sync_workplan_bindings(repo, "http://hub", "demo") assert result["ok"] is False and "unreachable" in result["error"] def test_reports_records_the_hub_does_not_have(self, tmp_path, monkeypatch): repo = self._repo(tmp_path) class R: def raise_for_status(self): return None def json(self): return {"updated": 1, "received": 2} monkeypatch.setattr(rr.httpx, "put", lambda *a, **k: R()) result = rr._sync_workplan_bindings(repo, "http://hub", "demo") assert result["unbound"] == 1