forked from quadsproject/badfish
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_context_manager.py
More file actions
741 lines (634 loc) · 30.6 KB
/
test_context_manager.py
File metadata and controls
741 lines (634 loc) · 30.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from badfish.main import Badfish, badfish_factory
from badfish.helpers.exceptions import BadfishException
class MockLogger:
def __init__(self):
self.debug_calls = []
self.warning_calls = []
self.info_calls = []
self.error_calls = []
def debug(self, message):
self.debug_calls.append(message)
def warning(self, message):
self.warning_calls.append(message)
def info(self, message):
self.info_calls.append(message)
def error(self, message):
self.error_calls.append(message)
MOCK_HOST = "test-host.example.com"
MOCK_USERNAME = "test_user"
MOCK_PASSWORD = "test_password"
MOCK_RETRIES = 5
class TestContextManager:
"""Test cases for the async context manager methods."""
@pytest.mark.asyncio
async def test_context_manager_successful_entry_exit(self):
"""Test successful entry and exit of the context manager."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
# Mock the init method
with patch.object(badfish_instance, "init", new_callable=AsyncMock) as mock_init:
with patch.object(badfish_instance, "delete_session", new_callable=AsyncMock) as mock_delete:
async with badfish_instance as bf:
assert bf is badfish_instance
mock_init.assert_called_once()
mock_delete.assert_called_once()
@pytest.mark.asyncio
async def test_context_manager_exception_handling(self):
"""Test that exceptions are properly handled and logged."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
# Mock the init method to raise an exception
with patch.object(badfish_instance, "init", new_callable=AsyncMock) as mock_init:
mock_init.side_effect = BadfishException("Test exception")
with patch.object(badfish_instance, "delete_session", new_callable=AsyncMock) as mock_delete:
with pytest.raises(BadfishException):
async with badfish_instance:
pass
# When init fails, __aexit__ is not called because the exception is raised before entering the context
mock_delete.assert_not_called()
@pytest.mark.asyncio
async def test_context_manager_direct_method_calls(self):
"""Test direct calls to __aenter__ and __aexit__ methods."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
with patch.object(badfish_instance, "init", new_callable=AsyncMock) as mock_init:
with patch.object(badfish_instance, "delete_session", new_callable=AsyncMock) as mock_delete:
# Test direct __aenter__ call
result = await badfish_instance.__aenter__()
assert result is badfish_instance
mock_init.assert_called_once()
# Test direct __aexit__ call
await badfish_instance.__aexit__(None, None, None)
mock_delete.assert_called_once()
@pytest.mark.asyncio
async def test_context_manager_nested_usage(self):
"""Test nested usage of the context manager."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
with patch.object(badfish_instance, "init", new_callable=AsyncMock) as mock_init:
with patch.object(badfish_instance, "delete_session", new_callable=AsyncMock) as mock_delete:
# First context manager usage
async with badfish_instance as bf1:
assert bf1 is badfish_instance
# Nested context manager usage (should call init again)
async with badfish_instance as bf2:
assert bf2 is badfish_instance
# delete_session should be called twice (once for each context)
assert mock_delete.call_count == 2
# init should be called twice (once for each context entry)
assert mock_init.call_count == 2
@pytest.mark.asyncio
async def test_context_manager_integration_with_factory(self):
"""Test context manager integration with badfish_factory."""
logger = MockLogger()
with patch.object(Badfish, "init", new_callable=AsyncMock) as mock_init:
with patch.object(Badfish, "delete_session", new_callable=AsyncMock) as mock_delete:
async with await badfish_factory(
MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES
) as badfish:
assert isinstance(badfish, Badfish)
assert badfish.host == MOCK_HOST
assert badfish.username == MOCK_USERNAME
assert badfish.password == MOCK_PASSWORD
# badfish_factory calls init, then context manager calls it again
assert mock_init.call_count == 2
mock_delete.assert_called_once()
@pytest.mark.asyncio
async def test_badfish_factory_without_logger(self):
"""Test badfish_factory creates a default logger when none is provided."""
with patch.object(Badfish, "init", new_callable=AsyncMock) as mock_init:
with patch.object(Badfish, "delete_session", new_callable=AsyncMock):
# Call badfish_factory WITHOUT a logger argument
badfish = await badfish_factory(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD)
assert isinstance(badfish, Badfish)
assert badfish.host == MOCK_HOST
assert badfish.username == MOCK_USERNAME
assert badfish.password == MOCK_PASSWORD
# Verify a logger was created
assert badfish.logger is not None
mock_init.assert_called_once()
@pytest.mark.asyncio
async def test_context_manager_session_cleanup(self):
"""Test that session cleanup happens even with exceptions."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
with patch.object(badfish_instance, "init", new_callable=AsyncMock):
with patch.object(badfish_instance, "delete_session", new_callable=AsyncMock) as mock_delete:
try:
async with badfish_instance:
raise ValueError("Unexpected error")
except ValueError:
pass
# delete_session should still be called despite the exception
mock_delete.assert_called_once()
@pytest.mark.asyncio
async def test_context_manager_init_failure(self):
"""Test context manager behavior when init fails."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
with patch.object(badfish_instance, "init", new_callable=AsyncMock) as mock_init:
mock_init.side_effect = BadfishException("Init failed")
with patch.object(badfish_instance, "delete_session", new_callable=AsyncMock) as mock_delete:
with pytest.raises(BadfishException):
async with badfish_instance:
pass
# When init fails, __aexit__ is not called
mock_delete.assert_not_called()
@pytest.mark.asyncio
async def test_context_manager_delete_session_failure(self):
"""Test context manager behavior when delete_session fails."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
with patch.object(badfish_instance, "init", new_callable=AsyncMock):
with patch.object(badfish_instance, "delete_session", new_callable=AsyncMock) as mock_delete:
mock_delete.side_effect = BadfishException("Delete session failed")
# The exception should be re-raised from __aexit__
with pytest.raises(BadfishException):
async with badfish_instance:
pass
class TestDeleteSession:
"""Test cases for the delete_session method."""
@pytest.mark.asyncio
async def test_delete_session_success(self):
"""Test successful session deletion."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = "/redfish/v1/SessionService/Sessions/123"
badfish_instance.token = "test_token"
# Mock the delete_request method
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
# Mock response with status 200
mock_response = MagicMock()
mock_response.status = 200
mock_delete_request.return_value = mock_response
await badfish_instance.delete_session()
# Verify delete_request was called with correct parameters
mock_delete_request.assert_called_once()
call_args = mock_delete_request.call_args
assert call_args[0][0] == f"https://{MOCK_HOST}/redfish/v1/SessionService/Sessions/123"
assert call_args[1]["headers"] == {"content-type": "application/json"}
# Verify session_id and token are cleared
assert badfish_instance.session_id is None
assert badfish_instance.token is None
# Verify success message was logged
assert any("Session successfully deleted" in call for call in logger.debug_calls)
@pytest.mark.asyncio
async def test_delete_session_404_status(self):
"""Test session deletion with 404 status (session not found)."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = "/redfish/v1/SessionService/Sessions/123"
badfish_instance.token = "test_token"
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
# Mock response with status 404
mock_response = MagicMock()
mock_response.status = 404
mock_delete_request.return_value = mock_response
await badfish_instance.delete_session()
# Verify session_id and token are still cleared
assert badfish_instance.session_id is None
assert badfish_instance.token is None
# Verify appropriate message was logged
assert any("Session not found (404)" in call for call in logger.debug_calls)
@pytest.mark.asyncio
async def test_delete_session_unexpected_status(self):
"""Test session deletion with unexpected status code."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = "/redfish/v1/SessionService/Sessions/123"
badfish_instance.token = "test_token"
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
# Mock response with unexpected status
mock_response = MagicMock()
mock_response.status = 500
mock_delete_request.return_value = mock_response
await badfish_instance.delete_session()
# Verify session_id and token are still cleared
assert badfish_instance.session_id is None
assert badfish_instance.token is None
# Verify warning was logged
assert any("Unexpected status 500" in call for call in logger.warning_calls)
@pytest.mark.asyncio
async def test_delete_session_exception_handling(self):
"""Test session deletion when delete_request raises an exception."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = "/redfish/v1/SessionService/Sessions/123"
badfish_instance.token = "test_token"
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
mock_delete_request.side_effect = BadfishException("Network error")
await badfish_instance.delete_session()
# Verify session_id and token are still cleared despite the exception
assert badfish_instance.session_id is None
assert badfish_instance.token is None
# Verify exception was logged as warning
assert any("Failed to delete session" in call for call in logger.warning_calls)
assert any("Network error" in call for call in logger.warning_calls)
@pytest.mark.asyncio
async def test_delete_session_no_session_id(self):
"""Test delete_session when no session_id is set."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = None
badfish_instance.token = "test_token"
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
await badfish_instance.delete_session()
# delete_request should not be called
mock_delete_request.assert_not_called()
# token should still be cleared
assert badfish_instance.token is None
# Verify appropriate message was logged
assert any("No session ID found" in call for call in logger.debug_calls)
@pytest.mark.asyncio
async def test_delete_session_cleanup_always_executes(self):
"""Test that cleanup (clearing session_id and token) always executes."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = "/redfish/v1/SessionService/Sessions/123"
badfish_instance.token = "test_token"
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
# Mock response to raise an exception
mock_delete_request.side_effect = Exception("Unexpected error")
# The method should not raise the exception due to try/finally
await badfish_instance.delete_session()
# Verify session_id and token are still cleared despite the exception
assert badfish_instance.session_id is None
assert badfish_instance.token is None
@pytest.mark.asyncio
async def test_delete_session_other_exception(self):
"""Test delete_session with non-BadfishException."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = "/redfish/v1/SessionService/Sessions/123"
badfish_instance.token = "test_token"
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
# Mock response to raise a different exception
mock_delete_request.side_effect = ValueError("Unexpected error")
# The method should not raise the exception due to try/finally
await badfish_instance.delete_session()
# Verify session_id and token are still cleared
assert badfish_instance.session_id is None
assert badfish_instance.token is None
@pytest.mark.asyncio
async def test_delete_session_outer_exception_handler(self):
"""Test the outer exception handler that ensures cleanup even for unexpected exceptions."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = "/redfish/v1/SessionService/Sessions/123"
badfish_instance.token = "test_token"
# Mock the logger to raise an exception when debug is called
# This will trigger the outer exception handler
with patch.object(logger, "debug", side_effect=RuntimeError("Logger error")):
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
# Mock successful response
mock_response = MagicMock()
mock_response.status = 200
mock_delete_request.return_value = mock_response
# The method should not raise the exception due to outer exception handler
await badfish_instance.delete_session()
# Verify session_id and token are still cleared despite the logger exception
assert badfish_instance.session_id is None
assert badfish_instance.token is None
# Verify delete_request was called
mock_delete_request.assert_called_once()
@pytest.mark.asyncio
async def test_delete_session_no_session_id_outer_exception(self):
"""Test outer exception handler when session_id is None and logger fails."""
logger = MockLogger()
badfish_instance = Badfish(MOCK_HOST, MOCK_USERNAME, MOCK_PASSWORD, logger, MOCK_RETRIES, _insecure=False)
badfish_instance.session_id = None
badfish_instance.token = "test_token"
# Mock the logger to raise an exception when debug is called
with patch.object(logger, "debug", side_effect=RuntimeError("Logger error")):
with patch.object(badfish_instance, "delete_request", new_callable=AsyncMock) as mock_delete_request:
# The method should not raise the exception due to outer exception handler
await badfish_instance.delete_session()
# Verify session_id and token are still cleared despite the logger exception
assert badfish_instance.session_id is None
assert badfish_instance.token is None
# Verify delete_request was not called (since session_id was None)
mock_delete_request.assert_not_called()
class TestExecuteBadfishSessionCleanup:
"""Test cases for session cleanup in execute_badfish function."""
@pytest.mark.asyncio
async def test_execute_badfish_session_cleanup_success(self):
"""Test successful session cleanup in execute_badfish."""
from badfish.main import execute_badfish
logger = MockLogger()
args = {
"u": MOCK_USERNAME,
"p": MOCK_PASSWORD,
"t": None,
"i": None,
"host_list": None,
"output": None,
"force": False,
"pxe": False,
"boot_to": None,
"boot_to_type": None,
"boot_to_mac": None,
"reboot_only": False,
"power_state": False,
"power_on": False,
"power_off": False,
"power_cycle": False,
"get_power_consumed": False,
"racreset": False,
"wait": False,
"bmc_reset": False,
"factory_reset": False,
"check_boot": False,
"toggle_boot_device": None,
"firmware_inventory": False,
"clear_jobs": False,
"check_job": None,
"ls_jobs": False,
"ls_interfaces": False,
"ls_processors": False,
"ls_gpu": False,
"ls_memory": False,
"ls_serial": False,
"check_virtual_media": False,
"unmount_virtual_media": False,
"mount_virtual_media": None,
"boot_to_virtual_media": False,
"check_remote_image": False,
"boot_remote_image": None,
"detach_remote_image": False,
"get_sriov": False,
"enable_sriov": False,
"disable_sriov": False,
"set_bios_attribute": False,
"get_bios_attribute": False,
"attribute": "",
"value": "",
"set_bios_password": False,
"remove_bios_password": False,
"new_password": "",
"old_password": "",
"screenshot": False,
"retries": MOCK_RETRIES,
"get_scp_targets": None,
"scp_targets": "ALL",
"scp_include_read_only": False,
"export_scp": None,
"import_scp": None,
"get_nic_fqdds": False,
"get_nic_attribute": None,
"set_nic_attribute": None,
"insecure": False,
}
with patch("badfish.main.badfish_factory") as mock_factory:
# Mock badfish instance
mock_badfish = MagicMock()
mock_badfish.session_id = "/redfish/v1/SessionService/Sessions/123"
mock_factory.return_value = mock_badfish
# Mock successful operation
with patch.object(mock_badfish, "delete_session", new_callable=AsyncMock) as mock_delete:
host, result = await execute_badfish(MOCK_HOST, args, logger)
assert host == MOCK_HOST
assert result is True
mock_delete.assert_called_once()
assert any("Session closed for host" in call for call in logger.debug_calls)
@pytest.mark.asyncio
async def test_execute_badfish_session_cleanup_failure(self):
"""Test session cleanup failure in execute_badfish."""
from badfish.main import execute_badfish
logger = MockLogger()
args = {
"u": MOCK_USERNAME,
"p": MOCK_PASSWORD,
"t": None,
"i": None,
"host_list": None,
"output": None,
"force": False,
"pxe": False,
"boot_to": None,
"boot_to_type": None,
"boot_to_mac": None,
"reboot_only": False,
"power_state": False,
"power_on": False,
"power_off": False,
"power_cycle": False,
"get_power_consumed": False,
"racreset": False,
"wait": False,
"bmc_reset": False,
"factory_reset": False,
"check_boot": False,
"toggle_boot_device": None,
"firmware_inventory": False,
"clear_jobs": False,
"check_job": None,
"ls_jobs": False,
"ls_interfaces": False,
"ls_processors": False,
"ls_gpu": False,
"ls_memory": False,
"ls_serial": False,
"check_virtual_media": False,
"unmount_virtual_media": False,
"mount_virtual_media": None,
"boot_to_virtual_media": False,
"check_remote_image": False,
"boot_remote_image": None,
"detach_remote_image": False,
"get_sriov": False,
"enable_sriov": False,
"disable_sriov": False,
"set_bios_attribute": False,
"get_bios_attribute": False,
"attribute": "",
"value": "",
"set_bios_password": False,
"remove_bios_password": False,
"new_password": "",
"old_password": "",
"screenshot": False,
"retries": MOCK_RETRIES,
"get_scp_targets": None,
"scp_targets": "ALL",
"scp_include_read_only": False,
"export_scp": None,
"import_scp": None,
"get_nic_fqdds": False,
"get_nic_attribute": None,
"set_nic_attribute": None,
"insecure": False,
}
with patch("badfish.main.badfish_factory") as mock_factory:
# Mock badfish instance
mock_badfish = MagicMock()
mock_badfish.session_id = "/redfish/v1/SessionService/Sessions/123"
mock_factory.return_value = mock_badfish
# Mock delete_session to raise an exception
with patch.object(mock_badfish, "delete_session", new_callable=AsyncMock) as mock_delete:
mock_delete.side_effect = BadfishException("Session cleanup failed")
host, result = await execute_badfish(MOCK_HOST, args, logger)
assert host == MOCK_HOST
assert result is True
mock_delete.assert_called_once()
# Verify warning message was logged
assert any("Failed to close session for" in call for call in logger.warning_calls)
assert any("Session cleanup failed" in call for call in logger.warning_calls)
@pytest.mark.asyncio
async def test_execute_badfish_no_session_cleanup(self):
"""Test execute_badfish when no session exists to clean up."""
from badfish.main import execute_badfish
logger = MockLogger()
args = {
"u": MOCK_USERNAME,
"p": MOCK_PASSWORD,
"t": None,
"i": None,
"host_list": None,
"output": None,
"force": False,
"pxe": False,
"boot_to": None,
"boot_to_type": None,
"boot_to_mac": None,
"reboot_only": False,
"power_state": False,
"power_on": False,
"power_off": False,
"power_cycle": False,
"get_power_consumed": False,
"racreset": False,
"wait": False,
"bmc_reset": False,
"factory_reset": False,
"check_boot": False,
"toggle_boot_device": None,
"firmware_inventory": False,
"clear_jobs": False,
"check_job": None,
"ls_jobs": False,
"ls_interfaces": False,
"ls_processors": False,
"ls_gpu": False,
"ls_memory": False,
"ls_serial": False,
"check_virtual_media": False,
"unmount_virtual_media": False,
"mount_virtual_media": None,
"boot_to_virtual_media": False,
"check_remote_image": False,
"boot_remote_image": None,
"detach_remote_image": False,
"get_sriov": False,
"enable_sriov": False,
"disable_sriov": False,
"set_bios_attribute": False,
"get_bios_attribute": False,
"attribute": "",
"value": "",
"set_bios_password": False,
"remove_bios_password": False,
"new_password": "",
"old_password": "",
"screenshot": False,
"retries": MOCK_RETRIES,
"get_scp_targets": None,
"scp_targets": "ALL",
"scp_include_read_only": False,
"export_scp": None,
"import_scp": None,
"get_nic_fqdds": False,
"get_nic_attribute": None,
"set_nic_attribute": None,
"insecure": False,
}
with patch("badfish.main.badfish_factory") as mock_factory:
# Mock badfish instance with no session_id
mock_badfish = MagicMock()
mock_badfish.session_id = None
mock_factory.return_value = mock_badfish
# Mock delete_session
with patch.object(mock_badfish, "delete_session", new_callable=AsyncMock) as mock_delete:
host, result = await execute_badfish(MOCK_HOST, args, logger)
assert host == MOCK_HOST
assert result is True
# delete_session should not be called when session_id is None
mock_delete.assert_not_called()
@pytest.mark.asyncio
async def test_execute_badfish_no_badfish_instance(self):
"""Test execute_badfish when badfish instance is None."""
from badfish.main import execute_badfish
logger = MockLogger()
args = {
"u": MOCK_USERNAME,
"p": MOCK_PASSWORD,
"t": None,
"i": None,
"host_list": None,
"output": None,
"force": False,
"pxe": False,
"boot_to": None,
"boot_to_type": None,
"boot_to_mac": None,
"reboot_only": False,
"power_state": False,
"power_on": False,
"power_off": False,
"power_cycle": False,
"get_power_consumed": False,
"racreset": False,
"wait": False,
"bmc_reset": False,
"factory_reset": False,
"check_boot": False,
"toggle_boot_device": None,
"firmware_inventory": False,
"clear_jobs": False,
"check_job": None,
"ls_jobs": False,
"ls_interfaces": False,
"ls_processors": False,
"ls_gpu": False,
"ls_memory": False,
"ls_serial": False,
"check_virtual_media": False,
"unmount_virtual_media": False,
"mount_virtual_media": None,
"boot_to_virtual_media": False,
"check_remote_image": False,
"boot_remote_image": None,
"detach_remote_image": False,
"get_sriov": False,
"enable_sriov": False,
"disable_sriov": False,
"set_bios_attribute": False,
"get_bios_attribute": False,
"attribute": "",
"value": "",
"set_bios_password": False,
"remove_bios_password": False,
"new_password": "",
"old_password": "",
"screenshot": False,
"retries": MOCK_RETRIES,
"get_scp_targets": None,
"scp_targets": "ALL",
"scp_include_read_only": False,
"export_scp": None,
"import_scp": None,
"get_nic_fqdds": False,
"get_nic_attribute": None,
"set_nic_attribute": None,
"insecure": False,
}
with patch("badfish.main.badfish_factory") as mock_factory:
# Mock badfish_factory to raise an exception
mock_factory.side_effect = BadfishException("Connection failed")
host, result = await execute_badfish(MOCK_HOST, args, logger)
assert host == MOCK_HOST
assert result is False
# No session cleanup should happen when badfish is None
assert not any("Session closed for host" in call for call in logger.debug_calls)
assert not any("Failed to close session for" in call for call in logger.warning_calls)