def test_find_new_messages(mocker, session): source = factory.Source() message_not_downloaded = factory.Message(source=source, is_downloaded=False, is_decrypted=None, content=None) message_decrypt_not_attempted = factory.Message(source=source, is_downloaded=True, is_decrypted=None, content=None) message_decrypt_failed = factory.Message(source=source, is_downloaded=True, is_decrypted=False, content=None) message_decrypt_success = factory.Message(source=source, is_downloaded=True, is_decrypted=True, content="teehee") session.add(source) session.add(message_decrypt_not_attempted) session.add(message_not_downloaded) session.add(message_decrypt_failed) session.add(message_decrypt_success) session.commit() messages = find_new_messages(session) assert len(messages) == 3 for message in messages: assert message.is_downloaded is False or message.is_decrypted is not True
def test_MessageDownloadJob_no_download_or_decrypt(mocker, homedir, session, session_maker): """ Test that an already-downloaded message successfully decrypts. Use the `homedir` fixture to get a GPG keyring. """ message_is_decrypted_false = factory.Message(source=factory.Source(), is_downloaded=True, is_decrypted=False, content=None) message_is_decrypted_none = factory.Message(source=factory.Source(), is_downloaded=True, is_decrypted=None, content=None) session.add(message_is_decrypted_false) session.add(message_is_decrypted_none) session.commit() gpg = GpgHelper(homedir, session_maker, is_qubes=False) job_1 = MessageDownloadJob(message_is_decrypted_false.uuid, homedir, gpg) job_2 = MessageDownloadJob(message_is_decrypted_none.uuid, homedir, gpg) mocker.patch.object(job_1.gpg, 'decrypt_submission_or_reply') mocker.patch.object(job_2.gpg, 'decrypt_submission_or_reply') api_client = mocker.MagicMock() path = os.path.join(homedir, 'data') api_client.download_submission = mocker.MagicMock(return_value=('', path)) job_1.call_api(api_client, session) job_2.call_api(api_client, session) assert message_is_decrypted_false.content is not None assert message_is_decrypted_false.is_downloaded is True assert message_is_decrypted_false.is_decrypted is True assert message_is_decrypted_none.content is not None assert message_is_decrypted_none.is_downloaded is True assert message_is_decrypted_none.is_decrypted is True
def main_window_no_key(mocker, homedir): # Setup app = QApplication([]) gui = Window() app.setActiveWindow(gui) gui.show() controller = Controller("http://localhost", gui, mocker.MagicMock(), homedir, proxy=False) controller.qubes = False gui.setup(controller) # Create a source widget source_list = gui.main_view.source_list source = factory.Source(public_key=None) source_list.update([source]) # Create a file widget, message widget, and reply widget mocker.patch("securedrop_client.gui.widgets.humanize_filesize", return_value="100") mocker.patch( "securedrop_client.gui.SecureQLabel.get_elided_text", return_value="1-yellow-doc.gz.gpg" ) source.collection.append( [ factory.File(source=source, filename="1-yellow-doc.gz.gpg"), factory.Message(source=source, filename="2-yellow-msg.gpg"), factory.Reply(source=source, filename="3-yellow-reply.gpg"), ] ) source_list.setCurrentItem(source_list.item(0)) gui.main_view.on_source_changed() yield gui # Teardown gui.login_dialog.close() app.exit()
def test_MessageSync_exception(homedir, config, mocker, source): """ Mostly here for code coverage- makes sure that if an exception is raised in the download thread, the code which catches it is actually run. Using the `config` fixture to ensure the config is written to disk. """ message = factory.Message(source=source['source']) api = mocker.MagicMock() is_qubes = False # mock to return the submission we want mocker.patch('securedrop_client.storage.find_new_messages', return_value=[message]) mocker.patch('securedrop_client.storage.find_new_files', return_value=[]) # mock to prevent GpgHelper from raising errors on init mocker.patch('securedrop_client.crypto.safe_mkdir') 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)
def test_MessageDownloadJob_with_crypto_error(mocker, homedir, session, session_maker): """ Test when a message successfully downloads, but does not successfully decrypt. Use the `homedir` fixture to get a GPG keyring. """ message = factory.Message(source=factory.Source(), is_downloaded=False, is_decrypted=None, content=None) session.add(message) session.commit() gpg = GpgHelper(homedir, session_maker, is_qubes=False) job = MessageDownloadJob(message.uuid, homedir, gpg) mocker.patch.object(job.gpg, 'decrypt_submission_or_reply', side_effect=CryptoError) api_client = mocker.MagicMock() api_client.default_request_timeout = mocker.MagicMock() path = os.path.join(homedir, 'data') api_client.download_submission = mocker.MagicMock(return_value=('', path)) with pytest.raises(DownloadDecryptionException): job.call_api(api_client, session) assert message.content is None assert message.is_downloaded is True assert message.is_decrypted is False
def test_MessageDownloadJob_happiest_path(mocker, homedir, session, session_maker): """ Test when a message successfully downloads and decrypts. Use the `homedir` fixture to get a GPG keyring. """ message = factory.Message(source=factory.Source(), is_downloaded=False, is_decrypted=None, content=None) session.add(message) session.commit() gpg = GpgHelper(homedir, session_maker, is_qubes=False) job = MessageDownloadJob(message.uuid, homedir, gpg) mocker.patch.object(job.gpg, 'decrypt_submission_or_reply') api_client = mocker.MagicMock() api_client.default_request_timeout = mocker.MagicMock() data_dir = os.path.join(homedir, 'data') api_client.download_submission = mocker.MagicMock(return_value=('', data_dir)) job.call_api(api_client, session) assert message.content is not None assert message.is_downloaded is True assert message.is_decrypted is True
def test_MessageDownloadJob_with_base_error(mocker, homedir, session, session_maker): """ Test when a message does not successfully download. """ message = factory.Message(source=factory.Source(), is_downloaded=False, is_decrypted=None, content=None) session.add(message) session.commit() gpg = GpgHelper(homedir, session_maker, is_qubes=False) job = MessageDownloadJob(message.uuid, homedir, gpg) api_client = mocker.MagicMock() api_client.default_request_timeout = mocker.MagicMock() mocker.patch.object(api_client, 'download_submission', side_effect=BaseError) decrypt_fn = mocker.patch.object(job.gpg, 'decrypt_submission_or_reply') with pytest.raises(BaseError): job.call_api(api_client, session) assert message.content is None assert message.is_downloaded is False assert message.is_decrypted is None decrypt_fn.assert_not_called()
def test_MessageSync_run_failure(mocker, source): message = factory.Message(source=source['source']) # mock the fetching of submissions mocker.patch('securedrop_client.storage.find_new_messages', return_value=[message]) mocker.patch('securedrop_client.storage.find_new_files', return_value=[]) # mock the handling of the downloaded files mocker.patch( 'securedrop_client.message_sync.storage.mark_file_as_downloaded') mocker.patch( 'securedrop_client.message_sync.storage.mark_message_as_downloaded') # mock the GpgHelper creation since we don't have directories/keys setup mocker.patch('securedrop_client.message_sync.GpgHelper') api = mocker.MagicMock() home = "/home/user/.sd" is_qubes = False 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_mark_message_as_downloaded(mocker): session = mocker.MagicMock() message = factory.Message(source=factory.Source(), is_downloaded=False) session.query().filter_by().one.return_value = message mark_as_downloaded(type(message), "mock_uuid", session) assert message.is_downloaded is True session.add.assert_called_once_with(message) session.commit.assert_called_once_with()
def test_get_message(mocker, session): source = factory.Source() message = factory.Message(source=source) session.add(source) session.add(message) result = get_message(session, message.uuid) assert result == message
def test_message_with_download_error(session, download_error_codes): m = factory.Message(is_decrypted=False, content=None) download_error = (session.query(DownloadError).filter_by( name=DownloadErrorCodes.DECRYPTION_ERROR.name).one()) m.download_error = download_error session.commit() classname = m.__class__.__name__.lower() assert str(m) == f"cannot decrypt {classname}"
def test_MessageSync_run_success(mocker, session, source): """Test when a message successfully downloads and decrypts.""" message = factory.Message(source=source['source'], is_downloaded=False, is_decrypted=None, content=None) session.add(message) session.commit() expected_content = 'foo' def set_object_decryption_status_with_content_side_effect( *nargs, **kwargs): message.content = expected_content # mock the fetching of submissions mocker.patch('securedrop_client.storage.find_new_messages', return_value=[message]) mock_download_status = mocker.patch( 'securedrop_client.message_sync.storage.mark_message_as_downloaded') mock_decryption_status = mocker.patch( 'securedrop_client.message_sync.storage.set_object_decryption_status_with_content', side_effect=set_object_decryption_status_with_content_side_effect) # don't create the signal mocker.patch('securedrop_client.message_sync.pyqtSignal') def mock_decrypt_submission_or_reply(filepath, plaintext_filename, is_doc): with open(plaintext_filename, 'w') as f: f.write(expected_content) # mock the GpgHelper creation since we don't have directories/keys setup mock_gpg_helper = mocker.MagicMock( decrypt_submission_or_reply=mock_decrypt_submission_or_reply) mocker.patch('securedrop_client.message_sync.GpgHelper', return_value=mock_gpg_helper) api = mocker.MagicMock(session=session) ms = MessageSync(api, 'mock', True) ms.session = session # "patch" it with a real session ms.api.download_submission = mocker.MagicMock( return_value=(1234, "/home/user/downloads/foo")) mock_message_ready = mocker.patch.object(ms, 'message_ready') # check that it runs without raising exceptions ms.run(False) mock_decryption_status.assert_called_once_with(message, api.session, True, expected_content) mock_download_status.called_once_with(message, api.mock_session) mock_message_ready.emit.assert_called_once_with(message.uuid, expected_content)
def test_MessageSync_run_decryption_error(mocker, session, source): """Test when a message successfully downloads, but does not successfully decrypt.""" message = factory.Message(source=source['source'], is_downloaded=False, is_decrypted=None, content=None) session.add(message) session.commit() # mock the fetching of submissions mocker.patch('securedrop_client.storage.find_new_messages', return_value=[message]) mock_download_status = mocker.patch( 'securedrop_client.message_sync.storage.mark_message_as_downloaded') mock_decryption_status = mocker.patch( 'securedrop_client.message_sync.storage.set_object_decryption_status_with_content' ) # 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') api = mocker.MagicMock(session=session) ms = MessageSync(api, 'mock', True) ms.session = session # "patch" it with a real session mocker.patch.object(ms.gpg, 'decrypt_submission_or_reply', side_effect=CryptoError) ms.api.download_submission = mocker.MagicMock( return_value=(1234, "/home/user/downloads/foo")) mock_message_ready = mocker.patch.object(ms, 'message_ready') # check that it runs without raising exceptions ms.run(False) mock_download_status.assert_called_once_with(message.uuid, session) mock_decryption_status.assert_called_once_with(message, api.session, False) mock_message_ready.emit.assert_called_once_with( message.uuid, '<Message not yet available>')
def test_MessageDownloadJob_message_already_downloaded(mocker, homedir, session, session_maker): """ Test that call_api just decrypts and returns uuid if already downloaded. """ message = factory.Message(source=factory.Source(), is_downloaded=True, is_decrypted=None) session.add(message) session.commit() gpg = GpgHelper(homedir, session_maker, is_qubes=False) job = MessageDownloadJob(message.uuid, homedir, gpg) mocker.patch.object(job.gpg, 'decrypt_submission_or_reply') api_client = mocker.MagicMock() download_fn = mocker.patch.object(api_client, 'download_submission') return_uuid = job.call_api(api_client, session) assert message.uuid == return_uuid assert message.is_decrypted is True download_fn.assert_not_called()
def test_set_object_decryption_status_with_content_with_content( session, source): ''' It should be possible to set the decryption status of an object in the database to `True`. Additionally, if `content` is passed in, the `content` column of the DB should take that value. This is to ensure that we have a way to decrypt something without violating the condition: if is_decrypted then content is not none. ''' message = factory.Message(source=source['source'], is_downloaded=True, is_decrypted=None, content=None) session.add(message) session.commit() content = 'test' set_object_decryption_status_with_content(message, session, True, content) # requery to ensure new object message = session.query(db.Message).get(message.id) assert message.is_decrypted is True assert message.content == content
def test_MessageDownloadJob_message_already_decrypted(mocker, homedir, session, session_maker, download_error_codes): """ Test that call_api just returns uuid if already decrypted. """ message = factory.Message(source=factory.Source(), is_downloaded=True, is_decrypted=True) session.add(message) session.commit() gpg = GpgHelper(homedir, session_maker, is_qubes=False) job = MessageDownloadJob(message.uuid, homedir, gpg) decrypt_fn = mocker.patch.object(job.gpg, "decrypt_submission_or_reply") api_client = mocker.MagicMock() api_client.default_request_timeout = mocker.MagicMock() download_fn = mocker.patch.object(api_client, "download_submission") return_uuid = job.call_api(api_client, session) assert message.uuid == return_uuid decrypt_fn.assert_not_called() download_fn.assert_not_called()
def test_set_message_decryption_status_with_content_with_content( session, source): """ It should be possible to set the decryption status of an object in the database to `True`. Additionally, if `content` is passed in, the `content` column of the DB should take that value. This is to ensure that we have a way to decrypt something without violating the condition: if is_decrypted then content is not none. """ message = factory.Message(source=source["source"], is_downloaded=True, is_decrypted=None, content=None) session.add(message) session.commit() set_message_or_reply_content(type(message), message.uuid, "mock_content", session) mark_as_decrypted(type(message), message.uuid, session) # requery to ensure new object message = session.query(db.Message).get(message.id) assert message.is_decrypted is True assert message.content == "mock_content"