def test_Client_on_file_download_Reply(homedir, config, mocker): """ If the handler is passed a reply, check the download_reply function is the one called against the API. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) source = factory.Source() journalist = db.User('Testy mcTestface') reply = db.Reply('reply-uuid', journalist, source, 'my-reply.gpg', 123) # Not a sdclientapi.Submission cl.call_api = mocker.MagicMock() cl.api = mocker.MagicMock() reply_sdk_object = mocker.MagicMock() mock_reply = mocker.patch('sdclientapi.Reply') mock_reply.return_value = reply_sdk_object cl.on_file_download(source, reply) cl.call_api.assert_called_once_with(cl.api.download_reply, cl.on_file_downloaded, cl.on_download_timeout, reply_sdk_object, cl.data_dir, current_object=reply)
def test_Client_timeout_cleanup_with_current_object(homedir, config, mocker): """ Ensure that cleanup is performed if an API call times out. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) mock_thread = mocker.MagicMock() mock_runner = mocker.MagicMock() mock_runner.current_object = 'current_object' mock_timer = mocker.MagicMock() cl.api_threads = { 'thread_uuid': { 'thread': mock_thread, 'runner': mock_runner, 'timer': mock_timer, } } cl.clean_thread = mocker.MagicMock() mock_user_callback = mocker.MagicMock() cl.timeout_cleanup('thread_uuid', mock_user_callback) assert mock_runner.i_timed_out is True cl.clean_thread.assert_called_once_with('thread_uuid') mock_user_callback.assert_called_once_with(current_object='current_object')
def test_Client_completed_api_call_without_current_object(safe_tmpdir): """ Ensure that cleanup is performed if an API call returns in the expected time. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) mock_thread = mock.MagicMock() mock_runner = mock.MagicMock() mock_runner.result = 'result' mock_runner.current_object = None mock_timer = mock.MagicMock() cl.api_threads = { 'thread_uuid': { 'thread': mock_thread, 'runner': mock_runner, 'timer': mock_timer, } } cl.clean_thread = mock.MagicMock() mock_user_callback = mock.MagicMock() cl.completed_api_call('thread_uuid', mock_user_callback) cl.clean_thread.assert_called_once_with('thread_uuid') mock_user_callback.assert_called_once_with('result') mock_timer.stop.assert_called_once_with()
def test_Client_on_file_click_Reply(safe_tmpdir): """ If the handler is passed a reply, check the download_reply function is the one called against the API. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) source = models.Source('source-uuid', 'testy-mctestface', False, 'mah pub key', 1, False, datetime.now()) journalist = models.User('Testy mcTestface') reply = models.Reply('reply-uuid', journalist, source, 'my-reply.gpg', 123) # Not a sdclientapi.Submission cl.call_api = mock.MagicMock() cl.api = mock.MagicMock() reply_sdk_object = mock.MagicMock() with mock.patch('sdclientapi.Reply') as mock_reply: mock_reply.return_value = reply_sdk_object cl.on_file_click(source, reply) cl.call_api.assert_called_once_with(cl.api.download_reply, cl.on_file_download, cl.on_download_timeout, reply_sdk_object, cl.data_dir, current_object=reply)
def test_Client_on_download_timeout(safe_tmpdir): mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.update_sources = mock.MagicMock() cl.api_runner = mock.MagicMock() current_object = mock.MagicMock() test_filename = "my-file-location-msg.gpg" cl.api_runner.result = ("", test_filename) cl.call_reset = mock.MagicMock() cl.set_status = mock.MagicMock() cl.on_download_timeout(current_object) cl.set_status.assert_called_once_with( "Connection to server timed out, please try again.")
def test_Client_completed_api_call_with_current_object(homedir, config, mocker): """ Ensure that cleanup is performed if an API call returns in the expected time. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) mock_thread = mocker.MagicMock() mock_runner = mocker.MagicMock() mock_runner.result = 'result' mock_runner.current_object = 'current_object' mock_timer = mocker.MagicMock() cl.api_threads = { 'thread_uuid': { 'thread': mock_thread, 'runner': mock_runner, 'timer': mock_timer, } } cl.clean_thread = mocker.MagicMock() mock_user_callback = mocker.MagicMock() cl.completed_api_call('thread_uuid', mock_user_callback) cl.clean_thread.assert_called_once_with('thread_uuid') mock_user_callback.assert_called_once_with('result', current_object='current_object') mock_timer.stop.assert_called_once_with()
def start_app(args, qt_args) -> None: """ Create all the top-level assets for the application, set things up and run the application. Specific tasks include: - set up logging. - create an application object. - create a window for the app. - create an API connection to the SecureDrop proxy. - create a SqlAlchemy session to local storage. - configure the client (logic) object. - ensure the application is setup in the default safe starting state. """ init(args.sdc_home) configure_logging(args.sdc_home) logging.info('Starting SecureDrop Client {}'.format(__version__)) app = QApplication(qt_args) app.setApplicationName('SecureDrop Client') app.setDesktopFileName('org.freedomofthepress.securedrop.client') app.setApplicationVersion(__version__) app.setAttribute(Qt.AA_UseHighDpiPixmaps) gui = Window() app.setWindowIcon(load_icon(gui.icon)) app.setStyleSheet(load_css('sdclient.css')) engine = make_engine(args.sdc_home) Session = sessionmaker(bind=engine) session = Session() client = Client("http://localhost:8081/", gui, session, args.sdc_home) client.setup() configure_signal_handlers(app) timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) sys.exit(app.exec_())
def test_Client_init(homedir, config, mocker): """ The passed in gui, app and session instances are correctly referenced and, where appropriate, have a reference back to the client. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost/', mock_gui, mock_session, homedir) assert cl.hostname == 'http://localhost/' assert cl.gui == mock_gui assert cl.session == mock_session assert cl.api_threads == {}
def test_Client_on_synced_remove_stale_sources(homedir, config, mocker): """ On an API sync, if a source no longer exists, remove it from the GUI. Using the `config` fixture to ensure the config is written to disk. """ mock_source_id = 'abc123' mock_conv_wrapper = 'mock' gui = mocker.Mock() gui.conversations = {mock_source_id: mock_conv_wrapper} mock_session = mocker.MagicMock() cl = Client('http://localhost', gui, mock_session, homedir) mock_source = mocker.Mock() mock_source.uuid = mock_source_id # not that the first item does *not* have the mock_source api_res = ([], mocker.MagicMock(), mocker.MagicMock()) cl.on_synced(api_res) # check that the uuid is not longer in the dict assert mock_source_id not in gui.conversations
def test_Client_on_download_timeout(homedir, config, mocker): ''' Using the `config` fixture to ensure the config is written to disk. ''' mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.update_sources = mocker.MagicMock() cl.api_runner = mocker.MagicMock() current_object = mocker.MagicMock() test_filename = "my-file-location-msg.gpg" cl.api_runner.result = ("", test_filename) cl.call_reset = mocker.MagicMock() cl.set_status = mocker.MagicMock() cl.on_download_timeout(current_object) cl.set_status.assert_called_once_with( "The connection to the SecureDrop server timed out. Please try again.")
def test_Client_on_file_downloaded_api_failure(safe_tmpdir): mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.update_sources = mock.MagicMock() cl.api_runner = mock.MagicMock() test_filename = "my-file-location-msg.gpg" cl.api_runner.result = ("", test_filename) cl.call_reset = mock.MagicMock() cl.set_status = mock.MagicMock() result_data = Exception('error message') submission_db_object = mock.MagicMock() submission_db_object.uuid = 'myuuid' submission_db_object.filename = 'filename' cl.on_file_downloaded(result_data, current_object=submission_db_object) cl.set_status.assert_called_once_with( "The file download failed. Please try again.")
def test_Client_on_synced_with_result(homedir, config, mocker): """ If there's a result to syncing, then update local storage. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.update_sources = mocker.MagicMock() cl.api_runner = mocker.MagicMock() cl.gpg = mocker.MagicMock() result_data = ('sources', 'submissions', 'replies') cl.update_sources = mocker.MagicMock() cl.api_runner = mocker.MagicMock() mock_source = mocker.MagicMock() mock_source.key = { 'type': 'PGP', 'public': PUB_KEY, } mock_sources = [mock_source] result_data = (mock_sources, 'submissions', 'replies') cl.call_reset = mocker.MagicMock() mock_storage = mocker.patch('securedrop_client.logic.storage') cl.on_synced(result_data) mock_storage.update_local_storage. \ assert_called_once_with(mock_session, mock_sources, "submissions", "replies", os.path.join(homedir, 'data')) cl.update_sources.assert_called_once_with()
def test_Client_completed_api_call_with_current_object(safe_tmpdir): """ Ensure that cleanup is performed if an API call returns in the expected time. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.timer = mock.MagicMock() cl.api_thread = mock.MagicMock() cl.api_runner = mock.MagicMock() cl.api_runner.current_object = mock.MagicMock() cl.call_reset = mock.MagicMock() mock_user_callback = mock.MagicMock() cl.result = mock.MagicMock() cl.completed_api_call(mock_user_callback) cl.call_reset.assert_called_once_with() mock_user_callback.call_count == 1 cl.timer.stop.assert_called_once_with()
def test_Client_on_authenticate_ok(safe_tmpdir): """ Ensure the client syncs when the user successfully logs in. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.sync_api = mock.MagicMock() cl.api = mock.MagicMock() cl.start_message_thread = mock.MagicMock() cl.start_reply_thread = mock.MagicMock() cl.api.username = '******' cl.on_authenticate(True) cl.sync_api.assert_called_once_with() cl.start_message_thread.assert_called_once_with() cl.gui.set_logged_in_as.assert_called_once_with('test') # Error status bar should be cleared cl.gui.update_error_status.assert_called_once_with("")
def test_Client_sync_api_not_authenticated(safe_tmpdir): """ If the API isn't authenticated, don't sync. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.authenticated = mock.MagicMock(return_value=False) cl.call_api = mock.MagicMock() cl.sync_api() assert cl.call_api.call_count == 0
def test_Client_update_sync(safe_tmpdir): """ Cause the UI to update with the result of self.last_sync(). """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.last_sync = mock.MagicMock() cl.update_sync() assert cl.last_sync.call_count == 1 cl.gui.show_sync.assert_called_once_with(cl.last_sync())
def test_Client_update_sync(homedir, config, mocker): """ Cause the UI to update with the result of self.last_sync(). Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.last_sync = mocker.MagicMock() cl.update_sync() assert cl.last_sync.call_count == 1 cl.gui.show_sync.assert_called_once_with(cl.last_sync())
def test_Client_sync_api_not_authenticated(homedir, config, mocker): """ If the API isn't authenticated, don't sync. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.authenticated = mocker.MagicMock(return_value=False) cl.call_api = mocker.MagicMock() cl.sync_api() assert cl.call_api.call_count == 0
def test_Client_sync_api(safe_tmpdir): """ Sync the API is authenticated. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.authenticated = mock.MagicMock(return_value=True) cl.call_api = mock.MagicMock() cl.sync_api() cl.call_api.assert_called_once_with(storage.get_remote_data, cl.on_synced, cl.on_sync_timeout, cl.api)
def test_Client_call_reset_no_thread(): """ The client will ignore an attempt to reset an API call is there's no such call "in flight". """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session) cl.finish_api_call = mock.MagicMock() cl.api_thread = None cl.call_reset() assert cl.finish_api_call.emit.call_count == 0
def test_Client_clean_thread_no_thread(safe_tmpdir): """ The client will ignore an attempt to reset an API call if there's no such call "in flight". """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.finish_api_call = mock.MagicMock() cl.api_threads = {'a': 'b'} cl.clean_thread('foo') assert len(cl.api_threads) == 1
def test_Client_on_authenticate_ok(homedir, config, mocker): """ Ensure the client syncs when the user successfully logs in. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.sync_api = mocker.MagicMock() cl.api = mocker.MagicMock() cl.start_message_thread = mocker.MagicMock() cl.start_reply_thread = mocker.MagicMock() cl.api.username = '******' cl.on_authenticate(True) cl.sync_api.assert_called_once_with() cl.start_message_thread.assert_called_once_with() cl.gui.set_logged_in_as.assert_called_once_with('test') # Error status bar should be cleared cl.gui.update_error_status.assert_called_once_with("")
def test_Client_start_reply_thread_if_already_running(homedir, config, mocker): """ Ensure that when starting the reply thread, we don't start another thread if it's already running. Instead, we just authenticate the existing thread. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.api = 'api object' cl.reply_sync = mocker.MagicMock() cl.reply_thread = mocker.MagicMock() cl.reply_thread.api = None cl.start_reply_thread() cl.reply_sync.api = cl.api cl.reply_thread.start.assert_not_called()
def test_Client_clean_thread_no_thread(homedir, config, mocker): """ The client will ignore an attempt to reset an API call if there's no such call "in flight". Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.finish_api_call = mocker.MagicMock() cl.api_threads = {'a': 'b'} cl.clean_thread('foo') assert len(cl.api_threads) == 1
def test_Client_update_star_not_logged_in(safe_tmpdir): """ Ensure that starring/unstarring a source when not logged in calls the method that displays an error status in the left sidebar. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) source_db_object = mock.MagicMock() cl.on_action_requiring_login = mock.MagicMock() cl.api = None cl.update_star(source_db_object) cl.on_action_requiring_login.assert_called_with()
def test_Client_on_file_downloaded_api_failure(homedir, config, mocker): ''' Using the `config` fixture to ensure the config is written to disk. ''' mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.update_sources = mocker.MagicMock() cl.api_runner = mocker.MagicMock() test_filename = "my-file-location-msg.gpg" cl.api_runner.result = ("", test_filename) cl.call_reset = mocker.MagicMock() cl.set_status = mocker.MagicMock() result_data = Exception('error message') submission_db_object = mocker.MagicMock() submission_db_object.uuid = 'myuuid' submission_db_object.filename = 'filename' cl.on_file_downloaded(result_data, current_object=submission_db_object) cl.set_status.assert_called_once_with( "The file download failed. Please try again.")
def test_Client_sync_api(homedir, config, mocker): """ Sync the API is authenticated. Using the `config` fixture to ensure the config is written to disk. """ mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) cl.authenticated = mocker.MagicMock(return_value=True) cl.call_api = mocker.MagicMock() cl.sync_api() cl.call_api.assert_called_once_with(storage.get_remote_data, cl.on_synced, cl.on_sync_timeout, cl.api)
def test_Client_on_authenticate_ok(): """ Ensure the client syncs when the user successfully logs in. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session) cl.sync_api = mock.MagicMock() cl.api = mock.MagicMock() cl.api.username = '******' cl.on_authenticate(True) cl.sync_api.assert_called_once_with() cl.gui.set_logged_in_as.assert_called_once_with('test')
def test_Client_on_synced_with_result(safe_tmpdir): """ If there's a result to syncing, then update local storage. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir)) cl.update_sources = mock.MagicMock() cl.api_runner = mock.MagicMock() result_data = ('sources', 'submissions', 'replies') cl.call_reset = mock.MagicMock() with mock.patch('securedrop_client.logic.storage') as mock_storage: cl.on_synced(result_data) mock_storage.update_local_storage.\ assert_called_once_with(mock_session, "sources", "submissions", "replies") cl.update_sources.assert_called_once_with()
def test_Client_on_synced_with_result(): """ If there's a result to syncing, then update local storage. """ mock_gui = mock.MagicMock() mock_session = mock.MagicMock() cl = Client('http://localhost', mock_gui, mock_session) cl.update_sources = mock.MagicMock() cl.api_runner = mock.MagicMock() cl.api_runner.result = (1, 2, 3, ) cl.call_reset = mock.MagicMock() with mock.patch('securedrop_client.logic.storage') as mock_storage: cl.on_synced(True) cl.call_reset.assert_called_once_with() mock_storage.update_local_storage.assert_called_once_with(mock_session, 1, 2, 3) cl.update_sources.assert_called_once_with()