@@ -60,18 +60,52 @@ def test_track_no_active_moves_no_pending(mock_shell):
6060 mock_shell .rich_console .print_info .assert_called_once_with ("No active or scheduled moves" )
6161
6262
63- def test_track_no_active_moves_with_pending (mock_shell ):
64- mock_shell .connection .api .get_all_move_status .return_value = []
65- mock_shell .connection .api .get_moves .return_value = [
66- {"host" : "host1.example.com" , "current" : "cloud01" , "new" : "cloud02" },
63+ @patch ("quads_client.commands.track.Live" )
64+ @patch ("quads_client.commands.track.time" )
65+ def test_track_no_active_moves_with_pending_transitions (mock_time , mock_live_cls , mock_shell ):
66+ """Pending moves poll at 10s until active moves appear, then transition to live tracking"""
67+ pending = [{"host" : "host1.example.com" , "current" : "cloud01" , "new" : "cloud02" }]
68+ active = [
69+ {
70+ "host" : "host1.example.com" ,
71+ "source_cloud" : "cloud01" ,
72+ "target_cloud" : "cloud02" ,
73+ "status" : "pending" ,
74+ }
75+ ]
76+ mock_shell .connection .api .get_all_move_status .side_effect = [
77+ [],
78+ active ,
79+ active ,
80+ [],
6781 ]
82+ mock_shell .connection .api .get_moves .return_value = pending
83+ mock_live_instance = MagicMock ()
84+ mock_live_cls .return_value .__enter__ = MagicMock (return_value = mock_live_instance )
85+ mock_live_cls .return_value .__exit__ = MagicMock (return_value = False )
6886
6987 cmd = TrackCommands (mock_shell )
7088 cmd .cmd_track ("" )
7189
72- mock_shell .rich_console .console .print .assert_called_once ()
73- table = mock_shell .rich_console .console .print .call_args [0 ][0 ]
74- assert table .title == "Scheduled Moves (awaiting next move cycle)"
90+ assert mock_live_cls .call_count == 2
91+
92+
93+ @patch ("quads_client.commands.track.Live" )
94+ @patch ("quads_client.commands.track.time" )
95+ def test_track_pending_ctrl_c_exits (mock_time , mock_live_cls , mock_shell ):
96+ """Ctrl+C during pending polling exits cleanly"""
97+ pending = [{"host" : "host1.example.com" , "current" : "cloud01" , "new" : "cloud02" }]
98+ mock_shell .connection .api .get_all_move_status .return_value = []
99+ mock_shell .connection .api .get_moves .return_value = pending
100+ mock_live_instance = MagicMock ()
101+ mock_live_cls .return_value .__enter__ = MagicMock (return_value = mock_live_instance )
102+ mock_live_cls .return_value .__exit__ = MagicMock (return_value = False )
103+ mock_time .sleep .side_effect = KeyboardInterrupt
104+
105+ cmd = TrackCommands (mock_shell )
106+ cmd .cmd_track ("" )
107+
108+ mock_shell .rich_console .console .print .assert_called ()
75109
76110
77111def test_track_no_active_move_single (mock_shell ):
@@ -84,16 +118,85 @@ def test_track_no_active_move_single(mock_shell):
84118 mock_shell .rich_console .print_info .assert_called_once_with ("No active or scheduled moves for host1" )
85119
86120
87- def test_track_no_active_single_with_pending (mock_shell ):
88- mock_shell .connection .api .get_move_status .return_value = None
89- mock_shell .connection .api .get_moves .return_value = [
90- {"host" : "host1" , "current" : "cloud01" , "new" : "cloud02" },
121+ @patch ("quads_client.commands.track.Live" )
122+ @patch ("quads_client.commands.track.time" )
123+ def test_track_single_pending_transitions (mock_time , mock_live_cls , mock_shell ):
124+ """Single-host pending polling transitions to live tracking when move starts"""
125+ pending = [{"host" : "host1" , "current" : "cloud01" , "new" : "cloud02" }]
126+ active = {
127+ "host" : "host1" ,
128+ "source_cloud" : "cloud01" ,
129+ "target_cloud" : "cloud02" ,
130+ "status" : "completed" ,
131+ }
132+ mock_shell .connection .api .get_move_status .side_effect = [
133+ None ,
134+ active ,
135+ active ,
91136 ]
137+ mock_shell .connection .api .get_moves .return_value = pending
138+ mock_live_instance = MagicMock ()
139+ mock_live_cls .return_value .__enter__ = MagicMock (return_value = mock_live_instance )
140+ mock_live_cls .return_value .__exit__ = MagicMock (return_value = False )
92141
93142 cmd = TrackCommands (mock_shell )
94143 cmd .cmd_track ("host1" )
95144
96- mock_shell .rich_console .console .print .assert_called_once ()
145+ assert mock_live_cls .call_count == 2
146+
147+
148+ @patch ("quads_client.commands.track.Live" )
149+ @patch ("quads_client.commands.track.time" )
150+ def test_track_pending_all_disappears (mock_time , mock_live_cls , mock_shell ):
151+ """Pending moves disappear after one refresh cycle"""
152+ pending = [{"host" : "host1.example.com" , "current" : "cloud01" , "new" : "cloud02" }]
153+ mock_shell .connection .api .get_all_move_status .return_value = []
154+ mock_shell .connection .api .get_moves .side_effect = [pending , pending , []]
155+ mock_live_instance = MagicMock ()
156+ mock_live_cls .return_value .__enter__ = MagicMock (return_value = mock_live_instance )
157+ mock_live_cls .return_value .__exit__ = MagicMock (return_value = False )
158+
159+ cmd = TrackCommands (mock_shell )
160+ cmd .cmd_track ("" )
161+
162+ mock_live_cls .assert_called_once ()
163+ mock_live_instance .update .assert_called_once ()
164+
165+
166+ @patch ("quads_client.commands.track.Live" )
167+ @patch ("quads_client.commands.track.time" )
168+ def test_track_pending_single_disappears (mock_time , mock_live_cls , mock_shell ):
169+ """Single-host pending move disappears after one refresh cycle"""
170+ pending = [{"host" : "host1" , "current" : "cloud01" , "new" : "cloud02" }]
171+ mock_shell .connection .api .get_move_status .side_effect = [None , None , None ]
172+ mock_shell .connection .api .get_moves .side_effect = [pending , pending , []]
173+ mock_live_instance = MagicMock ()
174+ mock_live_cls .return_value .__enter__ = MagicMock (return_value = mock_live_instance )
175+ mock_live_cls .return_value .__exit__ = MagicMock (return_value = False )
176+
177+ cmd = TrackCommands (mock_shell )
178+ cmd .cmd_track ("host1" )
179+
180+ mock_live_cls .assert_called_once ()
181+ mock_live_instance .update .assert_called_once ()
182+
183+
184+ @patch ("quads_client.commands.track.Live" )
185+ @patch ("quads_client.commands.track.time" )
186+ def test_track_pending_single_ctrl_c (mock_time , mock_live_cls , mock_shell ):
187+ """Ctrl+C during single-host pending polling exits cleanly"""
188+ pending = [{"host" : "host1" , "current" : "cloud01" , "new" : "cloud02" }]
189+ mock_shell .connection .api .get_move_status .return_value = None
190+ mock_shell .connection .api .get_moves .return_value = pending
191+ mock_live_instance = MagicMock ()
192+ mock_live_cls .return_value .__enter__ = MagicMock (return_value = mock_live_instance )
193+ mock_live_cls .return_value .__exit__ = MagicMock (return_value = False )
194+ mock_time .sleep .side_effect = KeyboardInterrupt
195+
196+ cmd = TrackCommands (mock_shell )
197+ cmd .cmd_track ("host1" )
198+
199+ mock_shell .rich_console .console .print .assert_called ()
97200
98201
99202def test_track_cloud_filter (mock_shell ):
@@ -221,3 +324,15 @@ def test_build_single_table_with_error(mock_shell):
221324 }
222325 table = cmd ._build_single_table (data )
223326 assert table .row_count >= 6
327+
328+
329+ def test_build_pending_table (mock_shell ):
330+ cmd = TrackCommands (mock_shell )
331+ pending = [
332+ {"host" : "host1.example.com" , "current" : "cloud01" , "new" : "cloud02" },
333+ {"host" : "host2.example.com" , "current" : "cloud01" , "new" : "cloud03" },
334+ ]
335+ table = cmd ._build_pending_table (pending )
336+ assert table .title == "Scheduled Moves (awaiting next move cycle)"
337+ assert "10s" in table .caption
338+ assert table .row_count == 2
0 commit comments