From a6cd92686c5f06feea5fa6f70a92f2d5ce712224 Mon Sep 17 00:00:00 2001 From: song <22676124+songoow@users.noreply.github.com> Date: Wed, 16 Sep 2026 10:48:40 -0400 Subject: [PATCH] test(project-lifecycle): patch refresh-state collaborators where they are called The refresh-state command moved out of `project_lifecycle` into `project_lifecycle_refresh_state`, which now owns the call sites for `refresh_state_run` and its collaborators. The goal-channel tests still patched `project_lifecycle`, so every case aborted with AttributeError: module 'loopx.cli_commands.project_lifecycle' has no attribute 'refresh_state_run' Retarget the 19 `monkeypatch.setattr` calls at the module that resolves the names, so the doubles intercept the real calls again. The command is still driven through `project_lifecycle.handle_project_lifecycle_command`, keeping the public entry point under test. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) --- .../test_project_lifecycle_goal_channel.py | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/tests/cli_commands/test_project_lifecycle_goal_channel.py b/tests/cli_commands/test_project_lifecycle_goal_channel.py index 21a3de412c..c4cb9aa69a 100644 --- a/tests/cli_commands/test_project_lifecycle_goal_channel.py +++ b/tests/cli_commands/test_project_lifecycle_goal_channel.py @@ -9,7 +9,10 @@ import pytest -from loopx.cli_commands import project_lifecycle +from loopx.cli_commands import ( + project_lifecycle, + project_lifecycle_refresh_state, +) from loopx.control_plane.capability_hooks import ( POST_WRITEBACK_HOOK_RESULT_SCHEMA_VERSION, PostWritebackHookRegistration, @@ -98,7 +101,7 @@ def test_refresh_state_applies_goal_channel_delivery_postcondition( ) -> None: captured: dict[str, Any] = {} monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "refresh_state_run", lambda **kwargs: { "ok": True, @@ -108,7 +111,7 @@ def test_refresh_state_applies_goal_channel_delivery_postcondition( }, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_explore_graph_after_material_refresh", lambda **kwargs: { "enabled": False, @@ -119,7 +122,7 @@ def test_refresh_state_applies_goal_channel_delivery_postcondition( }, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_human_gate_after_refresh", lambda **kwargs: gate_sync, ) @@ -147,7 +150,7 @@ def test_refresh_state_forwards_external_sink_suppression( ) -> None: captured_kwargs: dict[str, Any] = {} monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "refresh_state_run", lambda **kwargs: { "ok": True, @@ -157,7 +160,7 @@ def test_refresh_state_forwards_external_sink_suppression( }, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_explore_graph_after_material_refresh", lambda **kwargs: { "enabled": False, @@ -181,7 +184,7 @@ def sync_gate(**kwargs: Any) -> dict[str, Any]: } monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_human_gate_after_refresh", sync_gate, ) @@ -214,7 +217,7 @@ def _record_refresh(**kwargs: Any) -> dict[str, Any]: refresh_calls.append(kwargs) return {"ok": True, "appended": True, "dry_run": False} - monkeypatch.setattr(project_lifecycle, "refresh_state_run", _record_refresh) + monkeypatch.setattr(project_lifecycle_refresh_state, "refresh_state_run", _record_refresh) args = _args() args.usage_json = ( @@ -262,7 +265,7 @@ def test_refresh_state_redacts_goal_channel_exception_details( ) ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "refresh_state_run", lambda **kwargs: { "ok": True, @@ -272,7 +275,7 @@ def test_refresh_state_redacts_goal_channel_exception_details( }, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_explore_graph_after_material_refresh", lambda **kwargs: { "enabled": False, @@ -290,7 +293,7 @@ def fail_with_private_details(**kwargs: Any) -> dict[str, Any]: ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_human_gate_after_refresh", fail_with_private_details, ) @@ -355,7 +358,7 @@ def producer(value: Mapping[str, Any]) -> dict[str, object]: producer=producer, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "refresh_state_run", lambda **kwargs: { "ok": True, @@ -372,29 +375,29 @@ def producer(value: Mapping[str, Any]) -> dict[str, object]: }, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "read_heartbeat_settlement", lambda *args, **kwargs: SimpleNamespace( delivery=SimpleNamespace(failure=None) ), ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "settlement_result_payload", lambda result: {"status": "settled"}, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "resolve_runtime_root", lambda *args, **kwargs: runtime_root, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_explore_graph_after_material_refresh", lambda **kwargs: {"enabled": False}, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_human_gate_after_refresh", lambda **kwargs: {"enabled": False}, ) @@ -438,7 +441,7 @@ def projection(**kwargs: Any) -> dict[str, object]: return {} monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "refresh_state_run", lambda **kwargs: { "ok": True, @@ -448,12 +451,12 @@ def projection(**kwargs: Any) -> dict[str, object]: }, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_explore_graph_after_material_refresh", lambda **kwargs: {"enabled": False}, ) monkeypatch.setattr( - project_lifecycle, + project_lifecycle_refresh_state, "sync_human_gate_after_refresh", lambda **kwargs: {"enabled": False}, )