11import pytest
22import json
33import time
4- from unittest .mock import MagicMock , patch , Mock
4+ from unittest .mock import MagicMock , patch
55import os
66
77# Create all necessary mocks
2222# Apply mocks
2323with patch .dict ('sys.modules' , module_mocks ):
2424 # Import all required modules
25- from sdk .nexent .core .utils .observer import MessageObserver , ProcessType
25+ from sdk .nexent .core .utils .observer import MessageObserver
2626 from sdk .nexent .core .utils .tools_common_message import ToolSign
2727 # Import target module
2828 from sdk .nexent .core .tools .terminal_tool import TerminalTool
@@ -68,7 +68,8 @@ def mock_ssh_session():
6868@pytest .fixture
6969def terminal_tool (mock_observer ):
7070 """Create a TerminalTool instance for testing"""
71- with patch ('paramiko.SSHClient' ) as mock_client_class :
71+ with patch ('paramiko.SSHClient' ) as mock_client_class , \
72+ patch ('time.sleep' ):
7273 mock_client = MagicMock ()
7374 mock_client_class .return_value = mock_client
7475
@@ -86,7 +87,8 @@ def terminal_tool(mock_observer):
8687@pytest .fixture
8788def terminal_tool_no_observer ():
8889 """Create a TerminalTool instance without observer for testing"""
89- with patch ('paramiko.SSHClient' ) as mock_client_class :
90+ with patch ('paramiko.SSHClient' ) as mock_client_class , \
91+ patch ('time.sleep' ):
9092 mock_client = MagicMock ()
9193 mock_client_class .return_value = mock_client
9294
@@ -264,7 +266,8 @@ def test_create_session_no_password(self, mock_observer):
264266
265267 def test_get_session_creates_new (self , terminal_tool , mock_ssh_session ):
266268 """Test getting a new session"""
267- with patch .object (terminal_tool , '_create_session' ) as mock_create :
269+ with patch .object (terminal_tool , '_create_session' ) as mock_create , \
270+ patch ('time.sleep' ):
268271 mock_create .return_value = mock_ssh_session
269272
270273 session = terminal_tool ._get_session ("test_session" )
@@ -274,7 +277,8 @@ def test_get_session_creates_new(self, terminal_tool, mock_ssh_session):
274277
275278 def test_get_session_reuses_existing (self , terminal_tool , mock_ssh_session ):
276279 """Test reusing existing session"""
277- with patch .object (terminal_tool , '_create_session' ) as mock_create :
280+ with patch .object (terminal_tool , '_create_session' ) as mock_create , \
281+ patch ('time.sleep' ):
278282 mock_create .return_value = mock_ssh_session
279283 with patch .object (terminal_tool , '_is_session_alive' ) as mock_alive :
280284 mock_alive .return_value = True
@@ -289,7 +293,8 @@ def test_get_session_reuses_existing(self, terminal_tool, mock_ssh_session):
289293
290294 def test_get_session_recreates_dead_session (self , terminal_tool , mock_ssh_session ):
291295 """Test recreating dead session"""
292- with patch .object (terminal_tool , '_create_session' ) as mock_create :
296+ with patch .object (terminal_tool , '_create_session' ) as mock_create , \
297+ patch ('time.sleep' ):
293298 mock_create .return_value = mock_ssh_session
294299 with patch .object (terminal_tool , '_is_session_alive' ) as mock_alive :
295300 mock_alive .return_value = False
@@ -418,8 +423,12 @@ def test_execute_command_success(self, terminal_tool, mock_ssh_session):
418423 mock_channel .recv_ready .return_value = True
419424 mock_channel .recv .return_value = b"test output\n $ "
420425
421- with patch .object (terminal_tool , '_clean_output' ) as mock_clean :
426+ with patch .object (terminal_tool , '_clean_output' ) as mock_clean , \
427+ patch ('sdk.nexent.core.tools.terminal_tool.time.sleep' ), \
428+ patch ('sdk.nexent.core.tools.terminal_tool.time.time' ) as mock_time :
422429 mock_clean .return_value = "cleaned output"
430+ # Mock time progression to avoid infinite loop
431+ mock_time .side_effect = [0 , 0 , 0 , 31 ] # Simulate timeout after a few iterations
423432
424433 result = terminal_tool ._execute_command (mock_channel , "ls" , 30 )
425434
@@ -432,7 +441,8 @@ def test_execute_command_timeout(self, terminal_tool, mock_ssh_session):
432441 mock_channel = mock_ssh_session ["channel" ]
433442 mock_channel .recv_ready .return_value = False # No output
434443
435- with patch ('time.time' ) as mock_time :
444+ with patch ('time.time' ) as mock_time , \
445+ patch ('time.sleep' ):
436446 mock_time .side_effect = [0 , 0 , 35 ] # Timeout after 35 seconds
437447
438448 result = terminal_tool ._execute_command (mock_channel , "sleep 60" , 30 )
@@ -444,10 +454,11 @@ def test_execute_command_exception(self, terminal_tool, mock_ssh_session):
444454 mock_channel = mock_ssh_session ["channel" ]
445455 mock_channel .send .side_effect = Exception ("Send failed" )
446456
447- result = terminal_tool ._execute_command (mock_channel , "ls" , 30 )
448-
449- assert "Error executing command" in result
450- assert "Send failed" in result
457+ with patch ('time.sleep' ):
458+ result = terminal_tool ._execute_command (mock_channel , "ls" , 30 )
459+
460+ assert "Error executing command" in result
461+ assert "Send failed" in result
451462
452463 def test_execute_command_prompt_detection_with_no_more_data (self , terminal_tool , mock_ssh_session ):
453464 """Test command execution with prompt detection and no more data after prompt"""
@@ -458,7 +469,8 @@ def test_execute_command_prompt_detection_with_no_more_data(self, terminal_tool,
458469 mock_channel .recv_ready .side_effect = [True , False ]
459470 mock_channel .recv .return_value = b"file1.txt\n file2.txt\n $ "
460471
461- with patch .object (terminal_tool , '_clean_output' ) as mock_clean :
472+ with patch .object (terminal_tool , '_clean_output' ) as mock_clean , \
473+ patch ('time.sleep' ):
462474 mock_clean .return_value = "cleaned output"
463475
464476 result = terminal_tool ._execute_command (mock_channel , "ls" , 30 )
@@ -477,7 +489,8 @@ def test_execute_command_multiple_prompt_types(self, terminal_tool, mock_ssh_ses
477489 mock_channel .recv_ready .side_effect = [True , False ]
478490 mock_channel .recv .return_value = b"root@server:~# "
479491
480- with patch .object (terminal_tool , '_clean_output' ) as mock_clean :
492+ with patch .object (terminal_tool , '_clean_output' ) as mock_clean , \
493+ patch ('time.sleep' ):
481494 mock_clean .return_value = "cleaned output"
482495
483496 result = terminal_tool ._execute_command (mock_channel , "whoami" , 30 )
@@ -494,7 +507,8 @@ def test_execute_command_windows_prompt(self, terminal_tool, mock_ssh_session):
494507 mock_channel .recv_ready .side_effect = [True , False ]
495508 mock_channel .recv .return_value = b"C:\\ Users\\ test> "
496509
497- with patch .object (terminal_tool , '_clean_output' ) as mock_clean :
510+ with patch .object (terminal_tool , '_clean_output' ) as mock_clean , \
511+ patch ('time.sleep' ):
498512 mock_clean .return_value = "cleaned output"
499513
500514 result = terminal_tool ._execute_command (mock_channel , "dir" , 30 )
@@ -510,7 +524,8 @@ def test_execute_command_no_output_timeout(self, terminal_tool, mock_ssh_session
510524 # No data available, should timeout after 2 seconds of no output
511525 mock_channel .recv_ready .return_value = False
512526
513- with patch ('time.time' ) as mock_time :
527+ with patch ('time.time' ) as mock_time , \
528+ patch ('time.sleep' ):
514529 # Simulate time progression: start at 0, then 1 second, then 3 seconds (timeout)
515530 mock_time .side_effect = [0 , 1 , 3 ]
516531
@@ -525,7 +540,8 @@ class TestForwardMethod:
525540
526541 def test_forward_success (self , terminal_tool , mock_ssh_session ):
527542 """Test successful forward execution"""
528- with patch .object (terminal_tool , '_get_session' ) as mock_get_session :
543+ with patch .object (terminal_tool , '_get_session' ) as mock_get_session , \
544+ patch ('time.sleep' ):
529545 mock_get_session .return_value = mock_ssh_session
530546 with patch .object (terminal_tool , '_execute_command' ) as mock_execute :
531547 mock_execute .return_value = "command output"
@@ -543,7 +559,8 @@ def test_forward_success(self, terminal_tool, mock_ssh_session):
543559
544560 def test_forward_with_observer (self , terminal_tool , mock_ssh_session ):
545561 """Test forward execution with observer"""
546- with patch .object (terminal_tool , '_get_session' ) as mock_get_session :
562+ with patch .object (terminal_tool , '_get_session' ) as mock_get_session , \
563+ patch ('time.sleep' ):
547564 mock_get_session .return_value = mock_ssh_session
548565 with patch .object (terminal_tool , '_execute_command' ) as mock_execute :
549566 mock_execute .return_value = "command output"
@@ -564,7 +581,8 @@ def test_forward_with_observer(self, terminal_tool, mock_ssh_session):
564581
565582 def test_forward_without_observer (self , terminal_tool_no_observer , mock_ssh_session ):
566583 """Test forward execution without observer"""
567- with patch .object (terminal_tool_no_observer , '_get_session' ) as mock_get_session :
584+ with patch .object (terminal_tool_no_observer , '_get_session' ) as mock_get_session , \
585+ patch ('time.sleep' ):
568586 mock_get_session .return_value = mock_ssh_session
569587 with patch .object (terminal_tool_no_observer , '_execute_command' ) as mock_execute :
570588 mock_execute .return_value = "command output"
@@ -577,7 +595,8 @@ def test_forward_without_observer(self, terminal_tool_no_observer, mock_ssh_sess
577595
578596 def test_forward_exception (self , terminal_tool , mock_ssh_session ):
579597 """Test forward execution with exception"""
580- with patch .object (terminal_tool , '_get_session' ) as mock_get_session :
598+ with patch .object (terminal_tool , '_get_session' ) as mock_get_session , \
599+ patch ('time.sleep' ):
581600 mock_get_session .side_effect = Exception ("Session failed" )
582601
583602 result = terminal_tool .forward ("ls -la" , "test_session" , 30 )
@@ -590,7 +609,8 @@ def test_forward_exception(self, terminal_tool, mock_ssh_session):
590609
591610 def test_forward_default_parameters (self , terminal_tool , mock_ssh_session ):
592611 """Test forward execution with default parameters"""
593- with patch .object (terminal_tool , '_get_session' ) as mock_get_session :
612+ with patch .object (terminal_tool , '_get_session' ) as mock_get_session , \
613+ patch ('time.sleep' ):
594614 mock_get_session .return_value = mock_ssh_session
595615 with patch .object (terminal_tool , '_execute_command' ) as mock_execute :
596616 mock_execute .return_value = "output"
@@ -607,7 +627,9 @@ class TestIntegration:
607627
608628 def test_full_workflow (self , terminal_tool , mock_ssh_session ):
609629 """Test complete workflow from initialization to command execution"""
610- with patch ('paramiko.SSHClient' ) as mock_client_class :
630+ with patch ('paramiko.SSHClient' ) as mock_client_class , \
631+ patch ('sdk.nexent.core.tools.terminal_tool.time.sleep' ), \
632+ patch ('sdk.nexent.core.tools.terminal_tool.time.time' ) as mock_time :
611633 mock_client = MagicMock ()
612634 mock_client_class .return_value = mock_client
613635 mock_client .connect .return_value = None
@@ -617,6 +639,9 @@ def test_full_workflow(self, terminal_tool, mock_ssh_session):
617639 mock_ssh_session ["channel" ].recv_ready .return_value = True
618640 mock_ssh_session ["channel" ].recv .return_value = b"file1.txt\n file2.txt\n $ "
619641
642+ # Mock time progression to avoid infinite loop
643+ mock_time .side_effect = [0 , 0 , 0 , 31 , 1000 , 1001 , 1002 , 1003 , 1004 , 1005 ] # More values for other time.time() calls
644+
620645 # Execute command
621646 result = terminal_tool .forward ("ls" , "integration_test" , 30 )
622647
@@ -627,7 +652,8 @@ def test_full_workflow(self, terminal_tool, mock_ssh_session):
627652
628653 def test_multiple_commands_same_session (self , terminal_tool , mock_ssh_session ):
629654 """Test multiple commands using the same session"""
630- with patch .object (terminal_tool , '_get_session' ) as mock_get_session :
655+ with patch .object (terminal_tool , '_get_session' ) as mock_get_session , \
656+ patch ('time.sleep' ):
631657 mock_get_session .return_value = mock_ssh_session
632658 with patch .object (terminal_tool , '_execute_command' ) as mock_execute :
633659 mock_execute .return_value = "output"
0 commit comments