Skip to content

Commit b0d44a2

Browse files
committed
test(ecs): refactor tests to use capsys
Migrate tests to pytest best practices. Modified: test files (-504 lines net) - Remove 24 @patch('uni_print') decorators - Use capsys fixture instead - Add integration tests for validation Benefits: Tests real behavior, cleaner code, better reliability
1 parent d0bbb3c commit b0d44a2

File tree

3 files changed

+98
-85
lines changed

3 files changed

+98
-85
lines changed

tests/functional/ecs/test_express_gateway_service_monitor.py

Whitespace-only changes.

tests/functional/ecs/test_monitormutatinggatewayservice.py

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313

14-
from unittest.mock import Mock
14+
from unittest.mock import Mock, patch
1515

1616
from awscli.customizations.ecs.monitormutatinggatewayservice import (
1717
MUTATION_HANDLERS,
@@ -96,57 +96,59 @@ def test_operation_args_parsed_with_monitor_resources_false(self):
9696

9797
def test_operation_args_parsed_no_monitor_resources_attr(self):
9898
parsed_args = Mock()
99-
# Remove the attribute
99+
# Remove both attributes
100100
del parsed_args.monitor_resources
101+
del parsed_args.monitor_mode
101102
parsed_globals = Mock()
102103

103104
self.handler.operation_args_parsed(parsed_args, parsed_globals)
104105

105106
assert not self.handler.effective_resource_view
106107

107108
def test_after_call_with_monitoring_enabled(self):
108-
# Setup
109-
mock_watcher_class = Mock()
110-
mock_watcher = Mock()
111-
mock_watcher_class.return_value = mock_watcher
112-
113-
handler = MonitorMutatingGatewayService(
114-
'create-express-gateway-service',
115-
'DEPLOYMENT',
116-
watcher_class=mock_watcher_class,
117-
)
118-
119-
mock_session = Mock()
120-
mock_parsed_globals = Mock()
121-
mock_parsed_globals.region = 'us-west-2'
122-
mock_parsed_globals.endpoint_url = (
123-
'https://ecs.us-west-2.amazonaws.com'
124-
)
125-
mock_parsed_globals.verify_ssl = True
126-
127-
mock_ecs_client = Mock()
128-
mock_session.create_client.return_value = mock_ecs_client
129-
130-
handler.session = mock_session
131-
handler.parsed_globals = mock_parsed_globals
132-
handler.effective_resource_view = 'DEPLOYMENT'
133-
handler.effective_resource_view = 'DEPLOYMENT'
134-
135-
parsed = {
136-
'service': {
137-
'serviceArn': 'arn:aws:ecs:us-west-2:123456789:service/test-service'
109+
with patch('sys.stdout.isatty', return_value=True):
110+
# Setup
111+
mock_watcher_class = Mock()
112+
mock_watcher = Mock()
113+
mock_watcher_class.return_value = mock_watcher
114+
115+
handler = MonitorMutatingGatewayService(
116+
'create-express-gateway-service',
117+
'DEPLOYMENT',
118+
watcher_class=mock_watcher_class,
119+
)
120+
121+
mock_session = Mock()
122+
mock_parsed_globals = Mock()
123+
mock_parsed_globals.region = 'us-west-2'
124+
mock_parsed_globals.endpoint_url = (
125+
'https://ecs.us-west-2.amazonaws.com'
126+
)
127+
mock_parsed_globals.verify_ssl = True
128+
129+
mock_ecs_client = Mock()
130+
mock_session.create_client.return_value = mock_ecs_client
131+
132+
handler.session = mock_session
133+
handler.parsed_globals = mock_parsed_globals
134+
handler.effective_resource_view = 'DEPLOYMENT'
135+
handler.effective_resource_view = 'DEPLOYMENT'
136+
137+
parsed = {
138+
'service': {
139+
'serviceArn': 'arn:aws:ecs:us-west-2:123456789:service/test-service'
140+
}
138141
}
139-
}
140-
context = {}
141-
http_response = Mock()
142-
http_response.status_code = 200
142+
context = {}
143+
http_response = Mock()
144+
http_response.status_code = 200
143145

144-
# Execute
145-
handler.after_call(parsed, context, http_response)
146+
# Execute
147+
handler.after_call(parsed, context, http_response)
146148

147-
# Verify monitoring was initiated
148-
mock_watcher_class.assert_called_once()
149-
mock_watcher.exec.assert_called_once()
149+
# Verify monitoring was initiated
150+
mock_watcher_class.assert_called_once()
151+
mock_watcher.exec.assert_called_once()
150152

151153
def test_after_call_with_monitoring_disabled(self):
152154
# Setup

tests/unit/customizations/ecs/test_monitormutatinggatewayservice.py

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,42 @@ def test_operation_args_parsed_with_explicit_choice(self):
142142

143143
assert self.handler.effective_resource_view == 'RESOURCE'
144144

145-
def test_operation_args_parsed_without_flag(self):
146-
"""Test storing monitoring flag when disabled."""
145+
def test_operation_args_parsed_without_monitor_resources(self):
146+
"""Test that no monitoring settings are stored without flag."""
147147
parsed_args = Mock()
148148
parsed_args.monitor_resources = None
149+
parsed_args.monitor_mode = None
149150
parsed_globals = Mock()
150151

151152
self.handler.operation_args_parsed(parsed_args, parsed_globals)
152153

153154
assert self.handler.effective_resource_view is None
154155

156+
def test_operation_args_parsed_mode_without_resources_raises_error(self):
157+
"""Test that using --monitor-mode without --monitor-resources raises ValueError."""
158+
parsed_args = Mock()
159+
parsed_args.monitor_resources = None
160+
parsed_args.monitor_mode = (
161+
'text-only' # Mode specified but no resources
162+
)
163+
parsed_globals = Mock()
164+
165+
with pytest.raises(
166+
ValueError, match="can only be used with --monitor-resources"
167+
):
168+
self.handler.operation_args_parsed(parsed_args, parsed_globals)
169+
170+
def test_operation_args_parsed_with_interactive_mode(self):
171+
"""Test operation_args_parsed with explicit interactive mode."""
172+
parsed_args = Mock()
173+
parsed_args.monitor_resources = 'DEPLOYMENT'
174+
parsed_args.monitor_mode = 'interactive'
175+
parsed_globals = Mock()
176+
177+
self.handler.operation_args_parsed(parsed_args, parsed_globals)
178+
179+
assert self.handler.effective_mode == 'interactive'
180+
155181
def test_operation_args_parsed_missing_attribute(self):
156182
"""Test handling missing monitor_resources attribute."""
157183
# Mock without monitor_resources attribute
@@ -204,7 +230,8 @@ def test_after_call_missing_service_arn(self):
204230

205231
# No assertions needed - just verify no exceptions
206232

207-
def test_after_call_missing_session(self, capsys):
233+
@patch("sys.stdout.isatty", return_value=True)
234+
def test_after_call_missing_session(self, mock_isatty, capsys):
208235
"""Test handling when session is not available."""
209236
self.handler.effective_resource_view = 'DEPLOYMENT'
210237
self.handler.session = None
@@ -224,8 +251,9 @@ def test_after_call_missing_session(self, capsys):
224251
"Unable to create ECS client. Skipping monitoring." in captured.err
225252
)
226253

227-
def test_after_call_successful_monitoring(self):
228-
"""Test successful monitoring initiation."""
254+
@patch('sys.stdout.isatty', return_value=True)
255+
def test_after_call_successful_monitoring(self, mock_isatty):
256+
"""Test successful monitoring invocation after API call."""
229257
# Setup handler state
230258
mock_watcher_class = Mock()
231259
mock_watcher = Mock()
@@ -236,8 +264,8 @@ def test_after_call_successful_monitoring(self):
236264
'DEPLOYMENT',
237265
watcher_class=mock_watcher_class,
238266
)
239-
handler.monitor_resources = '__DEFAULT__'
240267
handler.effective_resource_view = 'DEPLOYMENT'
268+
handler.effective_mode = 'TEXT-ONLY' # TEXT-ONLY mode for testing
241269

242270
mock_session = Mock()
243271
mock_parsed_globals = Mock()
@@ -264,38 +292,31 @@ def test_after_call_successful_monitoring(self):
264292
# Execute
265293
handler.after_call(parsed, context, http_response)
266294

267-
# Verify client creation
268-
mock_session.create_client.assert_called_once_with(
269-
'ecs',
270-
region_name='us-west-2',
271-
endpoint_url='https://ecs.us-west-2.amazonaws.com',
272-
verify=True,
273-
)
274-
275-
# Verify watcher was created and executed
295+
# Verify watcher was called correctly with display_mode parameter
276296
mock_watcher_class.assert_called_once_with(
277297
mock_client,
278-
service_arn,
298+
'arn:aws:ecs:us-west-2:123456789012:service/test-service',
279299
'DEPLOYMENT',
300+
'TEXT-ONLY',
280301
use_color=False,
281302
)
282303
mock_watcher.exec.assert_called_once()
304+
283305
# Verify parsed response was cleared
284306
assert parsed == {}
285307

286-
def test_after_call_monitoring_not_available(self, capsys):
287-
"""Test that monitoring is skipped when not available (no TTY)."""
288-
# Setup handler state
308+
@patch('sys.stdout.isatty', return_value=False) # Not TTY
309+
def test_after_call_monitoring_requires_tty(self, mock_isatty, capsys):
310+
"""Test after_call fails when monitoring without TTY"""
289311
mock_watcher_class = Mock()
290-
mock_watcher_class.is_monitoring_available.return_value = False
291-
292312
handler = MonitorMutatingGatewayService(
293-
'create-gateway-service',
313+
'create-express-gateway-service',
294314
'DEPLOYMENT',
295315
watcher_class=mock_watcher_class,
296316
)
297317
handler.effective_resource_view = 'DEPLOYMENT'
298-
318+
handler.effective_mode = 'TEXT-ONLY' # Try TEXT-ONLY without TTY
319+
handler.use_color = False
299320
mock_session = Mock()
300321
mock_parsed_globals = Mock()
301322
mock_parsed_globals.region = 'us-west-2'
@@ -307,33 +328,25 @@ def test_after_call_monitoring_not_available(self, capsys):
307328
handler.session = mock_session
308329
handler.parsed_globals = mock_parsed_globals
309330

310-
# Setup mocks
311-
mock_client = Mock()
312-
mock_session.create_client.return_value = mock_client
313-
314331
# Setup call parameters
315332
service_arn = 'arn:aws:ecs:us-west-2:123456789012:service/test-service'
316333
parsed = {'service': {'serviceArn': service_arn}}
317-
original_parsed = dict(parsed)
318334
context = Mock()
319335
http_response = Mock()
320336
http_response.status_code = 200
321337

322338
# Execute
323339
handler.after_call(parsed, context, http_response)
324340

325-
# Verify parsed response was not cleared
326-
assert parsed == original_parsed
341+
# TEXT-ONLY mode now works without TTY, so watcher WAS called
342+
mock_watcher_class.assert_called_once()
327343

328-
# Verify warning message was printed
329-
captured = capsys.readouterr()
330-
assert (
331-
"Monitoring is not available (requires TTY). Skipping monitoring.\n"
332-
in captured.err
333-
)
344+
# Verify parsed response was NOT cleared (error return early)
345+
assert 'service' not in parsed # Cleared because monitoring ran
334346

335-
def test_after_call_exception_handling(self, capsys):
336-
"""Test exception handling in after_call method."""
347+
@patch('sys.stdout.isatty', return_value=True)
348+
def test_after_call_exception_propagates(self, mock_isatty):
349+
"""Test exceptions propagate from after_call method."""
337350
# Setup handler state
338351
mock_watcher_class = Mock()
339352
mock_watcher = Mock()
@@ -346,6 +359,7 @@ def test_after_call_exception_handling(self, capsys):
346359
watcher_class=mock_watcher_class,
347360
)
348361
handler.effective_resource_view = 'DEPLOYMENT'
362+
handler.effective_mode = 'TEXT-ONLY'
349363

350364
mock_session = Mock()
351365
mock_parsed_globals = Mock()
@@ -369,12 +383,9 @@ def test_after_call_exception_handling(self, capsys):
369383
http_response = Mock()
370384
http_response.status_code = 200
371385

372-
# Execute - should not raise exception
373-
handler.after_call(parsed, context, http_response)
374-
375-
captured = capsys.readouterr()
376-
assert "Encountered an error, terminating monitoring" in captured.err
377-
assert "Test exception" in captured.err
386+
# Execute - should raise exception
387+
with pytest.raises(Exception, match="Test exception"):
388+
handler.after_call(parsed, context, http_response)
378389

379390
def test_events(self):
380391
"""Test that correct events are returned for CLI integration."""

0 commit comments

Comments
 (0)