From 889642dbbebc5cfbef182911dd85eaa157159180 Mon Sep 17 00:00:00 2001 From: heartsucker Date: Wed, 9 Jan 2019 14:34:43 +0100 Subject: [PATCH] updated tests for signals/slots --- tests/gui/test_main.py | 6 +- tests/gui/test_widgets.py | 127 +++++++++++++++++++++++++------------ tests/test_app.py | 6 +- tests/test_logic.py | 56 +++++----------- tests/test_message_sync.py | 56 ++++++++-------- 5 files changed, 136 insertions(+), 115 deletions(-) diff --git a/tests/gui/test_main.py b/tests/gui/test_main.py index 082190f38a..1b62c096dc 100644 --- a/tests/gui/test_main.py +++ b/tests/gui/test_main.py @@ -5,6 +5,7 @@ from securedrop_client.gui.main import Window from securedrop_client.resources import load_icon from securedrop_client.db import Submission +from uuid import uuid4 app = QApplication([]) @@ -233,7 +234,8 @@ def test_conversation_pending_message(mocker): mock_source = mocker.MagicMock() mock_source.journalistic_designation = 'Testy McTestface' - submission = Submission(source=mock_source, uuid="test", size=123, + msg_uuid = str(uuid4()) + submission = Submission(source=mock_source, uuid=msg_uuid, size=123, filename="test.msg.gpg", download_url='http://test/test') @@ -248,7 +250,7 @@ def test_conversation_pending_message(mocker): w.show_conversation_for(mock_source) assert mocked_add_message.call_count == 1 - assert mocked_add_message.call_args == mocker.call("") + assert mocked_add_message.call_args == mocker.call(msg_uuid, "") def test_set_status(mocker): diff --git a/tests/gui/test_widgets.py b/tests/gui/test_widgets.py index 6a39549dd0..4e7fe7eb97 100644 --- a/tests/gui/test_widgets.py +++ b/tests/gui/test_widgets.py @@ -542,9 +542,33 @@ def test_SpeechBubble_init(mocker): mock_label = mocker.patch('securedrop_client.gui.widgets.QLabel') mocker.patch('securedrop_client.gui.widgets.QVBoxLayout') mocker.patch('securedrop_client.gui.widgets.SpeechBubble.setLayout') + mock_signal = mocker.Mock() + mock_connect = mocker.Mock() + mock_signal.connect = mock_connect - SpeechBubble('hello') + SpeechBubble('mock id', 'hello', mock_signal) mock_label.assert_called_once_with('hello') + assert mock_connect.called + + +def test_SpeechBubble_update_text(mocker): + """ + Check that the calling the slot updates the text. + """ + mocker.patch('securedrop_client.gui.widgets.QVBoxLayout') + mocker.patch('securedrop_client.gui.widgets.SpeechBubble.setLayout') + mock_signal = mocker.MagicMock() + + msg_id = 'abc123' + sb = SpeechBubble(msg_id, 'hello', mock_signal) + + new_msg = 'new message' + sb._update_text(msg_id, new_msg) + assert sb.message.text() == new_msg + + newer_msg = 'an even newer message' + sb._update_text(msg_id + 'xxxxx', newer_msg) + assert sb.message.text() == new_msg def test_SpeechBubble_html_init(mocker): @@ -555,8 +579,9 @@ def test_SpeechBubble_html_init(mocker): mock_label = mocker.patch('securedrop_client.gui.widgets.QLabel') mocker.patch('securedrop_client.gui.widgets.QVBoxLayout') mocker.patch('securedrop_client.gui.widgets.SpeechBubble.setLayout') + mock_signal = mocker.MagicMock() - SpeechBubble('hello') + SpeechBubble('mock id', 'hello', mock_signal) mock_label.assert_called_once_with('<b>hello</b>') @@ -565,48 +590,73 @@ def test_SpeechBubble_with_apostrophe_in_text(mocker): mock_label = mocker.patch('securedrop_client.gui.widgets.QLabel') mocker.patch('securedrop_client.gui.widgets.QVBoxLayout') mocker.patch('securedrop_client.gui.widgets.SpeechBubble.setLayout') + mock_signal = mocker.MagicMock() message = "I'm sure, you are reading my message." - SpeechBubble(message) + SpeechBubble('mock id', message, mock_signal) mock_label.assert_called_once_with(message) -def test_ConversationWidget_init_left(): +def test_ConversationWidget_init_left(mocker): """ Check the ConversationWidget is configured correctly for align-left. """ - cw = ConversationWidget('hello', align='left') + mock_signal = mocker.Mock() + mock_connect = mocker.Mock() + mock_signal.connect = mock_connect + + cw = ConversationWidget('mock id', 'hello', mock_signal, align='left') layout = cw.layout() + assert isinstance(layout.takeAt(0), QWidgetItem) assert isinstance(layout.takeAt(0), QSpacerItem) + assert mock_connect.called -def test_ConversationWidget_init_right(): +def test_ConversationWidget_init_right(mocker): """ Check the ConversationWidget is configured correctly for align-left. """ - cw = ConversationWidget('hello', align='right') + mock_signal = mocker.Mock() + mock_connect = mocker.Mock() + mock_signal.connect = mock_connect + + cw = ConversationWidget('mock id', 'hello', mock_signal, align='right') layout = cw.layout() + assert isinstance(layout.takeAt(0), QSpacerItem) assert isinstance(layout.takeAt(0), QWidgetItem) + assert mock_connect.called -def test_MessageWidget_init(): +def test_MessageWidget_init(mocker): """ Check the CSS is set as expected. """ - mw = MessageWidget('hello') + mock_signal = mocker.Mock() + mock_connected = mocker.Mock() + mock_signal.connect = mock_connected + + mw = MessageWidget('mock id', 'hello', mock_signal) ss = mw.styleSheet() + assert 'background-color' in ss + assert mock_connected.called -def test_ReplyWidget_init(): +def test_ReplyWidget_init(mocker): """ Check the CSS is set as expected. """ - rw = ReplyWidget('hello') + mock_signal = mocker.Mock() + mock_connected = mocker.Mock() + mock_signal.connect = mock_connected + + rw = ReplyWidget('mock id', 'hello', mock_signal) ss = rw.styleSheet() + assert 'background-color' in ss + assert mock_connected.called def test_FileWidget_init_left(mocker): @@ -680,78 +730,74 @@ def test_FileWidget_mousePressEvent_open(mocker): fw.controller.on_file_open.assert_called_once_with(submission) -def test_ConversationView_init(mocker): +def test_ConversationView_init(mocker, homedir): """ Ensure the conversation view has a layout to add widgets to. """ mocked_source = mocker.MagicMock() - cv = ConversationView(mocked_source) + mocked_controller = mocker.MagicMock() + cv = ConversationView(mocked_source, homedir, mocked_controller) assert isinstance(cv.conversation_layout, QVBoxLayout) -def test_ConversationView_setup(mocker): - """ - Ensure the controller is set - """ - mocked_source = mocker.MagicMock() - cv = ConversationView(mocked_source) - mock_controller = mocker.MagicMock() - cv.setup(mock_controller) - assert cv.controller == mock_controller - - -def test_ConversationView_move_to_bottom(mocker): +def test_ConversationView_move_to_bottom(mocker, homedir): """ Check the signal handler sets the correct value for the scrollbar to be the maximum possible value. """ mocked_source = mocker.MagicMock() - cv = ConversationView(mocked_source) + mocked_controller = mocker.MagicMock() + + cv = ConversationView(mocked_source, homedir, mocked_controller) + cv.scroll = mocker.MagicMock() cv.move_to_bottom(0, 6789) cv.scroll.verticalScrollBar().setValue.assert_called_once_with(6789) -def test_ConversationView_add_message(mocker): +def test_ConversationView_add_message(mocker, homedir): """ Adding a message results in a new MessageWidget added to the layout. """ mocked_source = mocker.MagicMock() - cv = ConversationView(mocked_source) - cv.controller = mocker.MagicMock() + mocked_controller = mocker.MagicMock() + + cv = ConversationView(mocked_source, homedir, mocked_controller) cv.conversation_layout = mocker.MagicMock() - cv.add_message('hello') + cv.add_message('mock id', 'hello') assert cv.conversation_layout.addWidget.call_count == 1 cal = cv.conversation_layout.addWidget.call_args_list assert isinstance(cal[0][0][0], MessageWidget) -def test_ConversationView_add_reply(mocker): +def test_ConversationView_add_reply(mocker, homedir): """ Adding a reply results in a new ReplyWidget added to the layout. """ mocked_source = mocker.MagicMock() - cv = ConversationView(mocked_source) - cv.controller = mocker.MagicMock() + mocked_controller = mocker.MagicMock() + + cv = ConversationView(mocked_source, homedir, mocked_controller) cv.conversation_layout = mocker.MagicMock() - cv.add_reply('hello') + cv.add_reply('mock id', 'hello') assert cv.conversation_layout.addWidget.call_count == 1 cal = cv.conversation_layout.addWidget.call_args_list assert isinstance(cal[0][0][0], ReplyWidget) -def test_ConversationView_add_downloaded_file(mocker): +def test_ConversationView_add_downloaded_file(mocker, homedir): """ Adding a file results in a new FileWidget added to the layout with the proper QLabel. """ mocked_source = mocker.MagicMock() - cv = ConversationView(mocked_source) - cv.controller = mocker.MagicMock() + mocked_controller = mocker.MagicMock() + + cv = ConversationView(mocked_source, homedir, mocked_controller) cv.conversation_layout = mocker.MagicMock() mock_source = mocker.MagicMock() @@ -769,14 +815,15 @@ def test_ConversationView_add_downloaded_file(mocker): assert isinstance(cal[0][0][0], FileWidget) -def test_ConversationView_add_not_downloaded_file(mocker): +def test_ConversationView_add_not_downloaded_file(mocker, homedir): """ Adding a file results in a new FileWidget added to the layout with the proper QLabel. """ mocked_source = mocker.MagicMock() - cv = ConversationView(mocked_source) - cv.controller = mocker.MagicMock() + mocked_controller = mocker.MagicMock() + + cv = ConversationView(mocked_source, homedir, mocked_controller) cv.conversation_layout = mocker.MagicMock() mock_source = mocker.MagicMock() diff --git a/tests/test_app.py b/tests/test_app.py index ca8d5e6b35..593363503e 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -234,7 +234,7 @@ def fake_known_args(): mock_start_app.assert_called_once_with(mock_args, mock_qt_args) -def test_signal_interception(mocker): +def test_signal_interception(mocker, homedir): # check that initializing an app calls configure_signal_handlers mocker.patch('securedrop_client.app.QApplication') mocker.patch('securedrop_client.app.prevent_second_instance') @@ -245,8 +245,10 @@ def test_signal_interception(mocker): mocker.patch('securedrop_client.logic.GpgHelper') mocker.patch('securedrop_client.app.configure_logging') mock_signal_handlers = mocker.patch('securedrop_client.app.configure_signal_handlers') + mock_args = mocker.Mock() + mock_args.sdc_home = homedir - start_app(mocker.MagicMock(), []) + start_app(mock_args, []) assert mock_signal_handlers.called # check that a signal interception calls quit on the app diff --git a/tests/test_logic.py b/tests/test_logic.py index bd8cd23f32..6310a8baac 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -680,59 +680,29 @@ def test_Client_update_sources(homedir, config, mocker): mock_gui.show_sources.assert_called_once_with(source_list) -def test_Client_update_conversation_view_current_source(homedir, config, mocker): +def test_Client_update_conversation_views(homedir, config, mocker): """ Ensure the UI displays the latest version of the messages/replies that have been downloaded/decrypted in the current conversation view. Using the `config` fixture to ensure the config is written to disk. """ - mock_gui = mocker.MagicMock() - mock_gui.current_source = 'teehee' - mock_gui.show_conversation_for = mocker.MagicMock() + mock_gui = mocker.Mock() + mock_conversation = mocker.MagicMock() + mock_update_conversation = mocker.MagicMock() + mock_conversation.update_conversation = mock_update_conversation + mock_gui.conversations = {'foo': mock_conversation} mock_session = mocker.MagicMock() # Since we use the set-like behavior of self.session # to check if the source is still persistent, let's mock that here - mock_session.__contains__ = mocker.MagicMock() - mock_session.__contains__.return_value = [mock_gui.current_source] + mock_session.__contains__ = mocker.Mock() + mock_session.__contains__.return_value = True mock_session.refresh = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) - cl.update_conversation_view() - mock_session.refresh.assert_called_with(mock_gui.current_source) - mock_gui.show_conversation_for.assert_called_once_with( - mock_gui.current_source) - - -def test_Client_update_conversation_deleted_source(homedir, config, mocker): - """ - Ensure the UI does not attempt to refresh and display a deleted - source. - Using the `config` fixture to ensure the config is written to disk. - """ - mock_gui = mocker.MagicMock() - mock_gui.current_source = 'teehee' - mock_gui.show_conversation_for = mocker.MagicMock() - mock_session = mocker.MagicMock() - mock_session.refresh = mocker.MagicMock() - cl = Client('http://localhost', mock_gui, mock_session, homedir) - cl.update_conversation_view() - mock_session.refresh.assert_not_called() - mock_gui.show_conversation_for.assert_not_called() - - -def test_Client_update_conversation_view_no_current_source(homedir, config, mocker): - """ - Ensure that if there is no current source (i.e. the user has not clicked - a source in the sidebar), the UI will not redraw the conversation view. - Using the `config` fixture to ensure the config is written to disk. - """ - mock_gui = mocker.MagicMock() - mock_gui.current_source = None - mock_session = mocker.MagicMock() - cl = Client('http://localhost', mock_gui, mock_session, homedir) - cl.update_conversation_view() - mock_gui.show_conversation_for.assert_not_called() + cl.update_conversation_views() + assert mock_session.refresh.called + assert mock_update_conversation.called def test_Client_unstars_a_source_if_starred(homedir, config, mocker): @@ -743,18 +713,22 @@ def test_Client_unstars_a_source_if_starred(homedir, config, mocker): mock_gui = mocker.MagicMock() mock_session = mocker.MagicMock() cl = Client('http://localhost', mock_gui, mock_session, homedir) + source_db_object = mocker.MagicMock() source_db_object.uuid = mocker.MagicMock() source_db_object.is_starred = True + cl.call_api = mocker.MagicMock() cl.api = mocker.MagicMock() cl.api.remove_star = mocker.MagicMock() cl.on_update_star_complete = mocker.MagicMock() cl.on_sidebar_action_timeout = mocker.MagicMock() + source_sdk_object = mocker.MagicMock() mock_source = mocker.patch('sdclientapi.Source') mock_source.return_value = source_sdk_object cl.update_star(source_db_object) + cl.call_api.assert_called_once_with( cl.api.remove_star, cl.on_update_star_complete, cl.on_sidebar_action_timeout, source_sdk_object) diff --git a/tests/test_message_sync.py b/tests/test_message_sync.py index 467e19b155..b7159add6f 100644 --- a/tests/test_message_sync.py +++ b/tests/test_message_sync.py @@ -30,20 +30,15 @@ def test_MessageSync_init(mocker): def test_MessageSync_run_success(mocker): submission = mocker.MagicMock() + submission.uuid = 'mock id' submission.download_url = "http://foo" submission.filename = "foo.gpg" - fh = mocker.MagicMock() - fh.name = "foo" - # mock the fetching of submissions mocker.patch('securedrop_client.storage.find_new_submissions', return_value=[submission]) - # mock the handling of the downloaded files - mocker.patch('shutil.move') - mocker.patch('os.unlink') - mocker.patch('tempfile.NamedTemporaryFile', return_value=fh) mocker.patch('securedrop_client.message_sync.storage.mark_file_as_downloaded') - mocker.patch('builtins.open', mocker.mock_open(read_data="blah")) + # don't create the signal + mocker.patch('securedrop_client.message_sync.pyqtSignal') # mock the GpgHelper creation since we don't have directories/keys setup mocker.patch('securedrop_client.message_sync.GpgHelper') @@ -54,9 +49,16 @@ def test_MessageSync_run_success(mocker): ms = MessageSync(api, home, is_qubes) ms.api.download_submission = mocker.MagicMock(return_value=(1234, "/home/user/downloads/foo")) + mock_message_downloaded = mocker.Mock() + mock_emit = mocker.Mock() + mock_message_downloaded.emit = mock_emit + mocker.patch.object(ms, 'message_downloaded', new=mock_message_downloaded) + # check that it runs without raising exceptions ms.run(False) + assert mock_emit.called + def test_MessageSync_exception(homedir, config, mocker): """ @@ -76,6 +78,8 @@ def test_MessageSync_exception(homedir, config, mocker): ms = MessageSync(api, str(homedir), is_qubes) mocker.patch.object(ms.gpg, 'decrypt_submission_or_reply', side_effect=CryptoError) + + # check that it runs without raising exceptions ms.run(False) @@ -84,17 +88,10 @@ def test_MessageSync_run_failure(mocker): submission.download_url = "http://foo" submission.filename = "foo.gpg" - fh = mocker.MagicMock() - fh.name = "foo" - # mock the fetching of submissions mocker.patch('securedrop_client.storage.find_new_submissions', return_value=[submission]) # mock the handling of the downloaded files - mocker.patch('shutil.move') - mocker.patch('os.unlink') mocker.patch('securedrop_client.message_sync.storage.mark_file_as_downloaded') - mocker.patch('tempfile.NamedTemporaryFile', return_value=fh) - mocker.patch('builtins.open', mocker.mock_open(read_data="blah")) # mock the GpgHelper creation since we don't have directories/keys setup mocker.patch('securedrop_client.message_sync.GpgHelper') @@ -105,30 +102,27 @@ def test_MessageSync_run_failure(mocker): ms = MessageSync(api, home, is_qubes) ms.api.download_submission = mocker.MagicMock(return_value=(1234, "/home/user/downloads/foo")) + # check that it runs without raising exceptions ms.run(False) def test_ReplySync_run_success(mocker): reply = mocker.MagicMock() + reply.uuid = 'mock id' reply.download_url = "http://foo" reply.filename = "foo.gpg" - fh = mocker.MagicMock() - fh.name = "foo" - api = mocker.MagicMock() home = "/home/user/.sd" is_qubes = True # mock the fetching of replies mocker.patch('securedrop_client.storage.find_new_replies', return_value=[reply]) + # don't create the signal + mocker.patch('securedrop_client.message_sync.pyqtSignal') # mock the handling of the replies - mocker.patch('shutil.move') - mocker.patch('os.unlink') - mocker.patch('securedrop_client.message_sync.storage.mark_file_as_downloaded') - mocker.patch('tempfile.NamedTemporaryFile', return_value=fh) + mocker.patch('securedrop_client.message_sync.storage.mark_reply_as_downloaded') mocker.patch('securedrop_client.message_sync.GpgHelper') - mocker.patch('builtins.open', mocker.mock_open(read_data="blah")) api = mocker.MagicMock() home = "/home/user/.sd" @@ -137,9 +131,16 @@ def test_ReplySync_run_success(mocker): ms = ReplySync(api, home, is_qubes) ms.api.download_reply = mocker.MagicMock(return_value=(1234, "/home/user/downloads/foo")) + mock_reply_downloaded = mocker.Mock() + mock_emit = mocker.Mock() + mock_reply_downloaded.emit = mock_emit + mocker.patch.object(ms, 'reply_downloaded', new=mock_reply_downloaded) + # check that it runs without raising exceptions ms.run(False) + assert mock_emit.called + def test_ReplySync_exception(mocker): """ @@ -157,6 +158,8 @@ def test_ReplySync_exception(mocker): mocker.patch("sdclientapi.sdlocalobjects.Reply", mocker.MagicMock(side_effect=Exception())) rs = ReplySync(api, home, is_qubes) + + # check that it runs without raise exceptions rs.run(False) @@ -165,18 +168,11 @@ def test_ReplySync_run_failure(mocker): reply.download_url = "http://foo" reply.filename = "foo.gpg" - fh = mocker.MagicMock() - fh.name = "foo" - # mock finding new replies mocker.patch('securedrop_client.storage.find_new_replies', return_value=[reply]) # mock handling the new reply - mocker.patch('shutil.move') - mocker.patch('os.unlink') mocker.patch('securedrop_client.message_sync.storage.mark_file_as_downloaded') - mocker.patch('tempfile.NamedTemporaryFile', return_value=fh) mocker.patch('securedrop_client.message_sync.GpgHelper') - mocker.patch('builtins.open', mocker.mock_open(read_data="blah")) api = mocker.MagicMock() home = "/home/user/.sd"