"""Test sftp connector.""" from src.common.exceptions import exceptions from src.common.connectors import sftp from unittest import mock import pytest def test_mkdir(sftp_server_and_conn_obj_pkey): """Test making new directories and setting cwd proper.""" sftpserver, conn_obj = sftp_server_and_conn_obj_pkey with sftpserver.serve_content({'a_dir': {'somefile.txt': 'File content'}}): c = sftp.Connection(**conn_obj) try: assert c.mkdir('/a_dir/b_dir/c_dir/d_dir/e_dir') is True assert c.scan_dir('/a_dir/b_dir/c_dir/d_dir') == ['e_dir'] assert c._sftp_client.getcwd() is None assert c.mkdir('/a_dir/b_dir/c_dir/d_dir/e_dir') is False assert c._sftp_client.getcwd() is None assert c.mkdir('/') is False assert c._sftp_client.getcwd() is None assert c.mkdir('') is False assert c._sftp_client.getcwd() is None except Exception as e: raise e finally: c.close() # don't hang if assertions fail def test_mkdir_partial_failure(sftp_server_and_conn_obj_pkey): """Test cleanup when mkdir fails in middle of operation.""" sftpserver, conn_obj = sftp_server_and_conn_obj_pkey with sftpserver.serve_content({'a_dir': {'somefile.txt': 'File content'}}): c = sftp.Connection(**conn_obj) try: # raise exception only on c_dir unmocked_func = c._sftp_client.mkdir def side_effect(arg): if arg == 'c_dir': raise OSError unmocked_func(arg) with mock.patch.object(c._sftp_client, 'mkdir', side_effect=side_effect) as m: # noqa:E501 # expect b_dir created, exception on c_dir with pytest.raises(FileNotFoundError): c.mkdir('/a_dir/b_dir/c_dir') assert m.call_args_list == [ mock.call('b_dir'), mock.call('c_dir') ] except Exception as e: raise e finally: c.close() # don't hang if assertions fail def test_upload(sftp_server_and_conn_obj_pkey): """Test uploading files.""" sftpserver, conn_obj = sftp_server_and_conn_obj_pkey with sftpserver.serve_content({'a_dir': {'somefile.txt': 'File content'}}): c = sftp.Connection(**conn_obj) try: c.upload_files([ ( b'data1', 'file_1.txt' ), ( b'data2', '/file_2.txt' ), ( b'data3', '/b_dir/file_3.txt' ) ]) assert c.scan_dir('/') == [ 'a_dir', 'b_dir', 'file_1.txt', 'file_2.txt' ] assert c.scan_dir('/b_dir') == ['file_3.txt'] except Exception as e: raise e finally: c.close() # don't hang if assertions fail @mock.patch.object(sftp.Connection, 'mkdir') @mock.patch('paramiko.SSHClient') def test_batch_mkdirs(mock_ssh, mock_mkdir, private_key): """Test internal client calls.""" mock_ssh_client = mock_ssh.return_value mock_sftp_client = mock_ssh_client.open_sftp.return_value assert not mock_ssh_client.open_sftp.called assert not mock_ssh_client.close.called conn_obj = { 'hostname': 'fake-sftp', 'port': 123, 'username': 'user123', 'pkey': private_key } c = sftp.Connection(**conn_obj) file_list = [ ( b'data1', 'file_1.txt' ), ( b'data2', '/file_2.txt' ), ( b'data3', '/b_dir/file_3.txt' ), ( b'data4', '/b_dir/c_dir/file_4.txt' ), ( b'data5', '/b_dir/file_5.txt' ), ] c.upload_files(file_list) c.close() assert mock_ssh_client.open_sftp.called assert mock_ssh_client.close.called assert mock_mkdir.call_args_list == [ mock.call('/b_dir/c_dir'), mock.call('/b_dir'), mock.call('/'), mock.call('') ] assert len(mock_sftp_client.putfo.call_args_list) == len(file_list) for call, file_pair in zip(mock_sftp_client.putfo.call_args_list, file_list): # noqa:E501 call[0][0].read() == file_pair[0] call[0][1] == file_pair[1] @mock.patch('paramiko.SSHClient') def test_init_ssh_client_exception(mock_ssh_client, sftp_server_and_conn_obj_pkey): # noqa:E501 """Test ssh client exception handled as retryable.""" conn_obj = sftp_server_and_conn_obj_pkey[1] mock_ssh_client.side_effect = Exception('error client') with pytest.raises(exceptions.RetryableException): sftp.Connection(**conn_obj)