def test_controller_continues_after_oserror(sftp, monkeypatch, capsys): """This test verifies that the controller prints an OSError message when it receives it and then continues execution (rather than quitting). """ oserror_message = "THIS IS A TEST" def mock_put(s, f): """A mock put action that simply raises a known exception.""" raise OSError(1, oserror_message) # Patch in our mocked put function. monkeypatch.setattr(put_file_onto_remote_server, "put", mock_put) # Our strategy here is to invoke two actions: our mock put, then an # invalid command. This will allow us to test for correct behavior, because # the controller will print its error message for unrecognized commands # if and only if it continues execution after handling the OSError. inputs = """ put file hooligan town banana republic """ with mock_input(inputs): main_loop(sftp) output = capsys.readouterr().out assert oserror_message in output # Now, we want to ensure that the error for unrecognized commands # is displayed AFTER the message we just asserted. So we'll split # the output on the message and check the second half. assert ERROR_MESSAGE_NOT_RECOGNIZED in \ output.split(oserror_message, 1)[1]
def test_invalid_commands_do_not_invoke_put_function(capsys, sftp): """Tests that writing invalid commands doesn't invoke the put action.""" # See tests/integration/test_close.py for details. with mock_input("putty"): main_loop(sftp) assert "not recognized" in capsys.readouterr().out
def test_invalid_commands_do_not_invoke_rm_function(capsys, sftp): """Tests that writing invalid commands doesn't invoke the rm action.""" # See tests/integration/test_close.py for details. test_cases = """rm rm one two """ with mock_input(test_cases): main_loop(sftp) assert SUCCESS_STRING not in capsys.readouterr().out
def test_invalid_commands_do_not_invoke_mput_function(capsys, sftp): """Tests that writing invalid commands doesn't invoke the mput action.""" inputs = """ cluckcluckcluck mput """ with mock_input(inputs): main_loop(sftp) assert SUCCESS_STRING not in capsys.readouterr().out
def test_invalid_commands_do_not_invoke_put_function(capsys, sftp): """Tests that writing invalid commands doesn't invoke the put action.""" # See tests/integration/test_close.py for details. test_input = """ definitely_not_a_command ls mydir """ with mock_input(test_input): main_loop(sftp) assert SUCCESS_STRING not in capsys.readouterr().out
def test_invalid_commands_do_not_invoke_close_function(capsys, sftp): """Tests that writing invalid commands doesn't invoke the close action.""" # Pass a fake command to main_loop() and verify that it detects that the # command is not recognized. Note that main_loop() terminates because # it reaches EOF in our simulated stdin, so it's impractical for us to # monkeypatch mock_close in and verify that it was NOT called. Even if # we could prevent main_loop from calling it, we would prevent main_loop # from terminating, most likely, so our test would never conclude. with mock_input("sayonara"): main_loop(sftp) assert "not recognized" in capsys.readouterr().out
def test_invalid_commands_do_not_invoke_put_r_function(capsys, sftp): """Tests that writing invalid commands doesn't invoke the rename action.""" # Test inputs. They need to be a single sequence, or we'll send EOF # and close our SFTP connection after the first test. test_inputs = """doesnotexist put -r put -r ham sandwich""" for test in test_inputs: with mock_input(test): main_loop(sftp) assert SUCCESS_STRING not in capsys.readouterr().out
def test_invalid_commands_do_not_invoke_lsearch_function(capsys, sftp): """Tests that writing invalid commands doesn't invoke the lsearch action.""" # See tests/integration/test_close.py for details. test_inputs = [ "doesnotexist", "lsearch", # No files "lsearch onefile twofile" ] for test in test_inputs: with mock_input(test): main_loop(sftp) assert SUCCESS_STRING not in capsys.readouterr().out
def test_lsearch_invokes_lsearch_action(capsys, monkeypatch, sftp): """Tests that writing text like 'lsearch myname' calls the lsearch action and passes 'myname' as name. """ # Replace the real action with our mocked function monkeypatch.setattr(lsearch, "search_local_files", mock_lsearch) # Pass in a valid input with mock_input("lsearch {}".format(NAME)): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_rename_invokes_rename_action(capsys, monkeypatch, sftp): """Tests that writing text like 'rename from to' calls the rename action and passes 'from' and 'to' as before and after, respectively. """ # Replace the real action with our mocked function monkeypatch.setattr(rename, "rename_remote_file", mock_rename) # Pass in a valid input with mock_input("rename {} {}".format(FROM, TO)): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_put_file_invokes_put_function(capsys, monkeypatch, sftp): """Tests that writing 'put file' calls the put action and passes 'file' as the filename. """ # Replace put_file_onto_remote_server.put with our mocked function monkeypatch.setattr(put_file_onto_remote_server, "put", mock_put) # Pass in a valid input with mock_input("put " + FILENAME): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_mkdir_invokes_create_folder_action(capsys, monkeypatch, sftp): """Tests that writing text like 'mkdir foldername' calls the put action and passes 'foldername' as the remote path to create. """ # Replace the real action with our mocked function monkeypatch.setattr(mkdir, "create_dir_remote", mock_mkdir) # Pass in a valid input with mock_input("mkdir {}".format(FOLDER)): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_put_file_invokes_put_function(capsys, monkeypatch, sftp): """Tests that writing 'mput file1 file1' calls the mput action and passes the list ["file1", "file2"] as filenames. """ # Replace put_file_onto_remote_server.put with our mocked function monkeypatch.setattr(mput, "put_multiple", mock_mput) # Pass in a valid input with mock_input("mput " + FILENAMES): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_chmod_invokes_chmod_action(capsys, monkeypatch, sftp): """Tests that writing text like 'chmod 555 mydir' calls the rename action and passes 777 and 'mydir' as permissions and filename, respectively. """ # Replace the real action with our mocked function monkeypatch.setattr(chmod, "change_permissions", mock_chmod) # Pass in a valid input with mock_input("chmod {} {}".format(MODE, PATH)): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_lls_invokes_list_files_local_action(capsys, monkeypatch, sftp): """Tests that writing 'lls' calls the 'list files local' action.""" # Replace the real function with our mocked function monkeypatch.setattr(list_files_local, "display_local_files", mock_list_local_files) # Pass in a valid input with mock_input("lls"): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_put_file_invokes_put_function(monkeypatch, capsys, sftp): """Tests that writing 'put file' calls the put action and passes 'file' as the filename. """ # Replace the action with our mocked function monkeypatch.setattr(list_files_remote, "list_dir", mock_list_dir) # Pass in a valid input with mock_input("ls"): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_lrename_invokes_rename_action(capsys, monkeypatch, sftp): """Tests that writing text like 'lrename from to' calls the put action and passes 'from' as the original filename and 'to' as the new filename. """ # Replace the real action with our mocked function monkeypatch.setattr(rename_files_local, "rename_local_file", mock_rename) # Pass in a valid input with mock_input("lrename {} {}".format(FROM, TO)): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_put_r_invokes_put_folder_action(capsys, monkeypatch, sftp): """Tests that writing text like 'lrename from to' calls the put action and passes 'from' as the original filename and 'to' as the new filename. """ # Replace the real action with our mocked function monkeypatch.setattr(put_folder, "put_r", mock_put_r) # Pass in a valid input with mock_input("put -r {}".format(FOLDER)): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_invalid_commands_do_not_invoke_rename_function(capsys, sftp): """Tests that writing invalid commands doesn't invoke the rename action.""" # See tests/integration/test_close.py for details. test_inputs = [ "doesnotexist", "lrename", # No files "lrename onefile", "lrename onefile twofile threefile" ] for test in test_inputs: with mock_input(test): main_loop(sftp) assert "not recognized" in capsys.readouterr().out
def test_close_commands_invoke_close_function(monkeypatch, capsys, sftp): """Tests that writing any valid close command at the program's main prompt, e.g. 'bye', 'exit', or 'quit', calls the close action. """ # Replace the real close action with our mocked version. monkeypatch.setattr(close, "close", mock_close) # For each input, invoke the main loop with an open connection, # pass in the input, and verify that mock_close() was called. for input in INPUTS: with mock_input(input): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_remove_file_invokes_remove_function(capsys, monkeypatch, sftp): """Tests that writing 'rm file' calls the remove_from_remote_server action and passes 'file' as the filename. """ # Replace remove_from_remote_server.remove_from_remote_server with our mocked function monkeypatch.setattr(remove_from_remote_server, "remove_from_remote_server", mock_rm) # Pass in a valid input with mock_input("rm " + FILENAME): main_loop(sftp) assert SUCCESS_STRING in capsys.readouterr().out
def test_main_loop_disconnection(sftp): """Tests that the app handles disconnects gracefully.""" # In order to test unexpected disconnects, we'll pass the controller # a disconnected sftp object and send a request take some basic action # that uses it. sftp.close() with mock_input("ls"): assert main_loop(sftp) == -1
def test_end_of_file(sftp, capsys): """Tests that the app handles the EOF character (e.g. Ctrl-D) gracefully. """ with mock_input(""): retval = main_loop(sftp) stdout = capsys.readouterr().out assert retval == 0, \ "Controller should return 0 after receiving EOF." assert ("connection closed" in stdout.lower()), \ "EOF response message should include 'connection closed'." assert stdout.startswith("\n", len(DEFAULT_USER_PROMPT)), \ "EOF response message should start with a newline."
def main(): # Catch-all try block for attempting to run the program. try: # Check that we received exactly one command-line argument. if len(sys.argv) != 2: print(INVALID_ARGUMENT_MESSAGE) return 1 # Split our one argument on "@", which will either give us # a username and a hostname or just a hostname (if there is no "@"). tup = sys.argv[1].split("@") if len(tup) > 2 or len(tup) == 0: # The user didn't pass in user@hostname or hostname, so we # can't proceed. We'll print the correct invocation and exit # so they can try again. print(INVALID_ARGUMENT_MESSAGE) return 1 if len(tup) == 2: # The user passed in user@hostname, so tup = [user, hostname]. username = tup[0] hostname = tup[1] else: # len(tup) == 1 # The user passed in hostname only, so we'll retrieve # the currently logged-in username from the OS. username = getpass.getuser() hostname = sys.argv[1] # Print username@hostname and prompt for password. # getpass.getpass() does not echo the input, as a security measure # # Note: if you run through PyCharm, getpass() may behave very oddly. # It should work correctly from the terminal, though. print(username + "@" + hostname + "'s", end=' ', flush=True) password = getpass.getpass() sftp = login.login(hostname, username, password) # Type check for correct allocation of pysftp.Connection object if type(sftp) == pysftp.Connection: print("Authentication success.") # Run the main controller loop, and when it returns, pass along its # return value as the program's exit code. return controller.main_loop(sftp) except Exception as error: print(error) # Disable exception printing before exit. Note: this is super-janky # and should never be used unless the program is about to close! # We just flatly replace the standard error output with a buffer # that we never read. # # We have to do this because pysftp raises an exception during # teardown if it was passed garbage inputs like hamsandwich@beepboop. # If we don't disable exception printing, Python prints a stack trace # for an ignored exception. Why? We can't catch the exception, # because when it's raised, we've already called sys.exit(), so # we don't have any code in scope. There's nowhere for the exception # to go. sys.stderr = StringIO() return 1