def test_suppresses_and_retries_on_ssh_error_exchange_identification(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh run command raises error the first time, succeeds the second time # noinspection PyUnusedLocal def ssh_run_command(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count < 2: raise SshcpError( "ssh_exchange_identification: read: Connection reset by peer" ) else: return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_run_command scanner.scan() self.assertEqual(2, self.mock_ssh.shell.call_count)
def test_raises_nonrecoverable_error_on_failed_scan(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh run command raises error the first time, succeeds the second time # noinspection PyUnusedLocal def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # md5sum check return b'' elif self.ssh_run_command_count == 2: # first try raise SshcpError("SystemScannerError: something failed") else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell with self.assertRaises(ScannerError) as ctx: scanner.scan() self.assertEqual( Localization.Error.REMOTE_SERVER_SCAN.format( "SystemScannerError: something failed"), str(ctx.exception)) self.assertFalse(ctx.exception.recoverable)
def test_appends_script_name_to_remote_path(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan") self.ssh_run_command_count = 0 # Ssh returns error for md5sum check, empty pickle dump for later commands def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # first try raise SshcpError("an ssh error") else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() # check for appended path ('script') self.mock_ssh.copy.assert_called_once_with( local_path=TestRemoteScanner.temp_scan_script, remote_path="/remote/path/to/scan/script")
def test_calls_correct_ssh_md5sum_command(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh returns error for md5sum check, empty pickle dump for later commands def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # first try raise SshcpError("an ssh error") else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() self.assertEqual(2, self.mock_ssh.shell.call_count) self.mock_ssh.shell.assert_has_calls([ call( "echo 'd41d8cd98f00b204e9800998ecf8427e /remote/path/to/scan/script' | md5sum -c --quiet" ), call(ANY) ])
def test_suppresses_and_retries_on_ssh_error_cannot_create_temp_dir(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh run command raises error the first time, succeeds the second time # noinspection PyUnusedLocal def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count < 2: raise SshcpError( "[23033] INTERNAL ERROR: cannot create temporary directory!" ) else: return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() self.assertEqual(2, self.mock_ssh.shell.call_count)
def test_installs_scan_script_on_any_md5sum_output(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh returns error for md5sum check, empty pickle dump for later commands def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # first try return "some output from md5sum".encode() else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() self.mock_ssh.copy.assert_called_once_with( local_path=TestRemoteScanner.temp_scan_script, remote_path="/remote/path/to/scan/script") self.mock_ssh.copy.reset_mock()
def test_raises_app_error_on_failed_ssh(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh run command raises error the first time, succeeds the second time # noinspection PyUnusedLocal def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count < 2: raise SshcpError("an ssh error") else: return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell with self.assertRaises(AppError) as ctx: scanner.scan() self.assertEqual(Localization.Error.REMOTE_SERVER_SCAN, str(ctx.exception))
def test_raises_nonrecoverable_error_on_md5sum_error(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh returns error for md5sum check, empty pickle dump for later commands def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # md5sum check raise SshcpError("an ssh error") else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell with self.assertRaises(ScannerError) as ctx: scanner.scan() self.assertEqual( Localization.Error.REMOTE_SERVER_INSTALL.format("an ssh error"), str(ctx.exception)) self.assertFalse(ctx.exception.recoverable)
def test_calls_correct_ssh_scan_command(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh returns error for md5sum check, empty pickle dump for later commands def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # md5sum check return b'' else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() self.assertEqual(2, self.mock_ssh.shell.call_count) self.mock_ssh.shell.assert_called_with( "'/remote/path/to/scan/script' '/remote/path/to/scan'")
def test_calls_correct_ssh_md5sum_command(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh returns error for md5sum check, empty pickle dump for later commands def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # first try return "".encode() else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() self.assertEqual(2, self.mock_ssh.shell.call_count) self.mock_ssh.shell.assert_has_calls([ call( "md5sum /remote/path/to/scan/script | awk '{print $1}' || echo" ), call(ANY) ])
def test_suppresses_and_retries_on_ssh_error_connection_timed_out(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh run command raises error the first time, succeeds the second time # noinspection PyUnusedLocal def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # md5sum check return b'' elif self.ssh_run_command_count == 2: # first try raise SshcpError( "connect to host host.remote.com port 2202: Connection timed out" ) else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() self.assertEqual(3, self.mock_ssh.shell.call_count)
def test_calls_correct_ssh_command(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") scanner.scan() self.mock_ssh.run_command.assert_called_once_with( "/remote/path/to/scan/script /remote/path/to/scan")
def test_raises_app_error_on_failed_scp(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") # noinspection PyUnusedLocal def scp_run_command(*args, **kwargs): raise ScpError("an ssh error") self.mock_scp.copy.side_effect = scp_run_command with self.assertRaises(AppError) as ctx: scanner.scan() self.assertEqual(Localization.Error.REMOTE_SERVER_INSTALL, str(ctx.exception))
def test_raises_nonrecoverable_error_on_mangled_output(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script" ) def ssh_shell(*args): return "mangled data".encode() self.mock_ssh.shell.side_effect = ssh_shell with self.assertRaises(ScannerError) as ctx: scanner.scan() self.assertEqual(Localization.Error.REMOTE_SERVER_SCAN.format("Invalid pickled data"), str(ctx.exception)) self.assertFalse(ctx.exception.recoverable)
def test_raises_nonrecoverable_error_on_failed_copy(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script" ) # noinspection PyUnusedLocal def ssh_copy(*args, **kwargs): raise SshcpError("an scp error") self.mock_ssh.copy.side_effect = ssh_copy with self.assertRaises(ScannerError) as ctx: scanner.scan() self.assertEqual(Localization.Error.REMOTE_SERVER_INSTALL.format("an scp error"), str(ctx.exception)) self.assertFalse(ctx.exception.recoverable)
def test_raises_app_error_on_mangled_output(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") # Ssh run command raises error the first time, succeeds the second time # noinspection PyUnusedLocal def ssh_shell(*args): return "mangled data".encode() self.mock_ssh.shell.side_effect = ssh_shell with self.assertRaises(AppError) as ctx: scanner.scan() self.assertEqual(Localization.Error.REMOTE_SERVER_SCAN, str(ctx.exception))
def test_fails_after_max_retries_on_suppressed_error(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") # noinspection PyUnusedLocal def ssh_run_command(*args): raise SshError("bash: /remote/path/to/scan: Text file busy") self.mock_ssh.run_command.side_effect = ssh_run_command with self.assertRaises(AppError) as ctx: scanner.scan() self.assertEqual(Localization.Error.REMOTE_SERVER_SCAN, str(ctx.exception)) # initial try + 5 retries self.assertEqual(6, self.mock_ssh.run_command.call_count)
def test_skips_install_on_md5sum_match(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh returns empty on md5sum, empty pickle dump for later commands def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # first try return "d41d8cd98f00b204e9800998ecf8427e".encode() else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() self.mock_ssh.copy.assert_not_called() self.mock_ssh.copy.reset_mock() # should not be called the second time either scanner.scan() self.mock_ssh.copy.assert_not_called()
def test_suppresses_and_retries_on_ssh_error_text_file_busy(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh run command raises error the first time, succeeds the second time # noinspection PyUnusedLocal def ssh_run_command(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count < 2: raise SshError("bash: /remote/path/to/scan: Text file busy") else: return pickle.dumps([]) self.mock_ssh.run_command.side_effect = ssh_run_command scanner.scan() self.assertEqual(2, self.mock_ssh.run_command.call_count)
def test_correctly_initializes_scp(self): self.scp_args = {} def mock_scp_ctor(**kwargs): self.scp_args = kwargs self.mock_ssh_cls.side_effect = mock_scp_ctor scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.assertIsNotNone(scanner) self.assertEqual("my remote address", self.scp_args["host"]) self.assertEqual(1234, self.scp_args["port"]) self.assertEqual("my remote user", self.scp_args["user"])
def test_recovers_from_failed_ssh(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_password="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") self.ssh_run_command_count = 0 # Ssh run command succeeds first time, raises error the second time, fine after that # noinspection PyUnusedLocal def ssh_shell(*args): self.ssh_run_command_count += 1 if self.ssh_run_command_count == 1: # md5sum check return b'' elif self.ssh_run_command_count == 2: # first try return pickle.dumps([]) elif self.ssh_run_command_count == 3: # second try raise SshcpError("an ssh error") else: # later tries return pickle.dumps([]) self.mock_ssh.shell.side_effect = ssh_shell scanner.scan() # no error first time with self.assertRaises(ScannerError): scanner.scan() scanner.scan() self.assertEqual(4, self.mock_ssh.shell.call_count)
def test_installs_scan_script_on_first_scan(self): scanner = RemoteScanner( remote_address="my remote address", remote_username="******", remote_port=1234, remote_path_to_scan="/remote/path/to/scan", local_path_to_scan_script=TestRemoteScanner.temp_scan_script, remote_path_to_scan_script="/remote/path/to/scan/script") scanner.scan() self.mock_scp.copy.assert_called_once_with( local_path=TestRemoteScanner.temp_scan_script, remote_path="/remote/path/to/scan/script") self.mock_scp.copy.reset_mock() # should not be called the second time scanner.scan() self.mock_scp.copy.assert_not_called()