11"""Tests for clayde.orchestrator — event-driven loop."""
22
3+ from contextlib import ExitStack , contextmanager
34from datetime import datetime , timezone
45from unittest .mock import MagicMock , patch
56
@@ -24,66 +25,60 @@ def _mock_settings(enabled=False, github_token="tok", github_username="ClaydeCod
2425 return s
2526
2627
28+ @contextmanager
29+ def _patched_main (enabled = True , claude_available = True , assigned = (), state = None ):
30+ """Patch every external dependency `main()` touches.
31+
32+ Yields a dict of name → mock so individual tests can assert on call behavior.
33+ """
34+ targets = {
35+ "get_settings" : {"return_value" : _mock_settings (enabled = enabled )},
36+ "setup_logging" : {},
37+ "init_tracer" : {},
38+ "_configure_global_git_identity" : {},
39+ "is_claude_available" : {"return_value" : claude_available },
40+ "get_github_client" : {},
41+ "get_assigned_issues" : {"return_value" : list (assigned )},
42+ "load_state" : {"return_value" : state if state is not None else {"issues" : {}}},
43+ "_prune_closed_issues" : {},
44+ "_handle_issue" : {},
45+ }
46+ with ExitStack () as stack :
47+ yield {
48+ name : stack .enter_context (patch (f"clayde.orchestrator.{ name } " , ** kwargs ))
49+ for name , kwargs in targets .items ()
50+ }
51+
52+
2753class TestMain :
2854 def test_exits_when_disabled (self ):
29- with patch ("clayde.orchestrator.setup_logging" ), \
30- patch ("clayde.orchestrator.get_settings" , return_value = _mock_settings (enabled = False )):
55+ with _patched_main (enabled = False ):
3156 with pytest .raises (SystemExit ) as exc_info :
3257 main ()
3358 assert exc_info .value .code == 0
3459
3560 def test_returns_when_claude_unavailable (self ):
36- with patch ("clayde.orchestrator.get_settings" , return_value = _mock_settings (enabled = True )), \
37- patch ("clayde.orchestrator.setup_logging" ), \
38- patch ("clayde.orchestrator.init_tracer" ), \
39- patch ("clayde.orchestrator._configure_global_git_identity" ), \
40- patch ("clayde.orchestrator.is_claude_available" , return_value = False ), \
41- patch ("clayde.orchestrator.get_github_client" ) as mock_gc :
61+ with _patched_main (claude_available = False ) as mocks :
4262 main ()
43- mock_gc .assert_not_called ()
63+ mocks [ "get_github_client" ] .assert_not_called ()
4464
4565 def test_returns_when_no_assigned_issues (self ):
46- with patch ("clayde.orchestrator.get_settings" , return_value = _mock_settings (enabled = True )), \
47- patch ("clayde.orchestrator.setup_logging" ), \
48- patch ("clayde.orchestrator.init_tracer" ), \
49- patch ("clayde.orchestrator._configure_global_git_identity" ), \
50- patch ("clayde.orchestrator.is_claude_available" , return_value = True ), \
51- patch ("clayde.orchestrator.get_github_client" ), \
52- patch ("clayde.orchestrator.get_assigned_issues" , return_value = []), \
53- patch ("clayde.orchestrator.load_state" , return_value = {"issues" : {}}):
66+ with _patched_main (assigned = []):
5467 main ()
5568
5669 def test_calls_handle_issue_for_each_assigned (self ):
5770 issue = MagicMock ()
5871 issue .html_url = "https://github.com/o/r/issues/1"
59- with patch ("clayde.orchestrator.get_settings" , return_value = _mock_settings (enabled = True )), \
60- patch ("clayde.orchestrator.setup_logging" ), \
61- patch ("clayde.orchestrator.init_tracer" ), \
62- patch ("clayde.orchestrator._configure_global_git_identity" ), \
63- patch ("clayde.orchestrator.is_claude_available" , return_value = True ), \
64- patch ("clayde.orchestrator.get_github_client" ), \
65- patch ("clayde.orchestrator.get_assigned_issues" , return_value = [issue ]), \
66- patch ("clayde.orchestrator.load_state" , return_value = {"issues" : {}}), \
67- patch ("clayde.orchestrator._prune_closed_issues" ), \
68- patch ("clayde.orchestrator._handle_issue" ) as mock_handle :
72+ with _patched_main (assigned = [issue ]) as mocks :
6973 main ()
70- mock_handle .assert_called_once ()
74+ mocks [ "_handle_issue" ] .assert_called_once ()
7175
7276 def test_main_calls_prune (self ):
7377 issue = MagicMock ()
7478 issue .html_url = "https://github.com/o/r/issues/1"
75- with patch ("clayde.orchestrator.get_settings" , return_value = _mock_settings (enabled = True )), \
76- patch ("clayde.orchestrator.setup_logging" ), \
77- patch ("clayde.orchestrator.init_tracer" ), \
78- patch ("clayde.orchestrator._configure_global_git_identity" ), \
79- patch ("clayde.orchestrator.is_claude_available" , return_value = True ), \
80- patch ("clayde.orchestrator.get_github_client" ), \
81- patch ("clayde.orchestrator.get_assigned_issues" , return_value = [issue ]), \
82- patch ("clayde.orchestrator.load_state" , return_value = {"issues" : {}}), \
83- patch ("clayde.orchestrator._prune_closed_issues" ) as mock_prune , \
84- patch ("clayde.orchestrator._handle_issue" ):
79+ with _patched_main (assigned = [issue ]) as mocks :
8580 main ()
86- mock_prune .assert_called_once ()
81+ mocks [ "_prune_closed_issues" ] .assert_called_once ()
8782
8883
8984class TestHandleIssue :
0 commit comments