def test_SourceList_maintains_selection(mocker): """ Maintains the selected item if present in new list """ sl = SourceList(None) sources = [factory.Source(), factory.Source()] sl.setup(mocker.MagicMock()) sl.update(sources) sl.setCurrentItem(sl.itemAt(0, 0)) sl.update(sources) assert sl.currentItem() assert sl.itemWidget(sl.currentItem()).source.id == sources[0].id
def test_star_if_unstar(homedir, mocker, session, session_maker): ''' Check if we call add_star method if a source is not stared. ''' source = factory.Source() session.add(source) session.commit() api_client = mocker.MagicMock() api_client.add_star = mocker.MagicMock() mock_sdk_source = mocker.Mock() mock_source_init = mocker.patch('securedrop_client.logic.sdclientapi.Source', return_value=mock_sdk_source) job = UpdateStarJob( source.uuid, source.is_starred ) job.call_api(api_client, session) # ensure we call add_star with right uuid for source mock_source_init.assert_called_once_with(uuid=source.uuid) api_client.add_star.assert_called_once_with(mock_sdk_source)
def test_send_reply_failure_when_repr_is_none(homedir, mocker, session, session_maker): ''' Check that the SendReplyJob api call results in a SendReplyJobError and nothing else, e.g. no TypeError, when an api call results in an exception that returns None for __repr__ (regression test). ''' class MockException(Exception): def __repr__(self): return None source = factory.Source(uuid='mock_reply_uuid') session.add(source) session.commit() api_client = mocker.MagicMock() mocker.patch.object(api_client, 'reply_source', side_effect=MockException('mock')) gpg = GpgHelper(homedir, session_maker, is_qubes=False) encrypt_fn = mocker.patch.object(gpg, 'encrypt_to_source') job = SendReplyJob(source.uuid, 'mock_reply_uuid', 'mock_message', gpg) with pytest.raises( SendReplyJobError, match= r'Failed to send reply for source mock_reply_uuid due to Exception: mock' ): job.call_api(api_client, session) encrypt_fn.assert_called_once_with(source.uuid, 'mock_message') replies = session.query(db.Reply).filter_by(uuid='mock_reply_uuid').all() assert len(replies) == 0
def test_send_reply_failure_timeout_error( homedir, mocker, session, session_maker, reply_status_codes, exception ): """ Check that if the SendReplyJob api call fails because of a RequestTimeoutError or ServerConnectionError that a SendReplyJobTimeoutError is raised. """ source = factory.Source() session.add(source) draft_reply = factory.DraftReply(uuid="mock_reply_uuid") session.add(draft_reply) session.commit() api_client = mocker.MagicMock() mocker.patch.object(api_client, "reply_source", side_effect=exception) gpg = GpgHelper(homedir, session_maker, is_qubes=False) encrypt_fn = mocker.patch.object(gpg, "encrypt_to_source") job = SendReplyJob(source.uuid, "mock_reply_uuid", "mock_message", gpg) with pytest.raises(SendReplyJobTimeoutError): job.call_api(api_client, session) encrypt_fn.assert_called_once_with(source.uuid, "mock_message") replies = session.query(db.Reply).filter_by(uuid="mock_reply_uuid").all() assert len(replies) == 0 # Ensure that the draft reply is still in the db drafts = session.query(db.DraftReply).filter_by(uuid="mock_reply_uuid").all() assert len(drafts) == 1
def test_string_representation_of_message(): source = factory.Source() msg = Message(source=source, uuid="test", size=123, filename="1-test.docx", download_url='http://test/test') msg.__str__() msg.content = "hello" msg.__str__()
def test_string_representation_of_file(): source = factory.Source() file_ = File(source=source, uuid="test", size=123, filename="1-test.docx", download_url='http://test/test') file_.__str__() file_.is_downloaded = True file_.__str__()
def test_source_server_collection(): # Create some test submissions and replies source = factory.Source() file_ = File(source=source, uuid="test", size=123, filename="2-test.doc.gpg", download_url='http://test/test') message = Message(source=source, uuid="test", size=123, filename="3-test.doc.gpg", download_url='http://test/test') user = User(username='******') reply = Reply(source=source, journalist=user, filename="1-reply.gpg", size=1234, uuid='test') draft_reply = DraftReply(source=source, journalist=user, uuid='test', timestamp=datetime.datetime(2002, 6, 6, 6, 0)) source.files = [file_] source.messages = [message] source.replies = [reply] source.draftreplies = [draft_reply] # Now these items should be in the source collection in the proper order assert source.server_collection[0] == reply assert source.server_collection[1] == file_ assert source.server_collection[2] == message # Drafts do not appear in the server_collection, they are local only. assert draft_reply not in source.server_collection
def test_source_collection_ordering_with_multiple_draft_replies(): # Create some test submissions, replies, and draft replies. source = factory.Source() file_1 = File(source=source, uuid="test", size=123, filename="1-test.doc.gpg", download_url='http://test/test') message_2 = Message(source=source, uuid="test", size=123, filename="2-test.doc.gpg", download_url='http://test/test') user = User(username='******') reply_3 = Reply(source=source, journalist=user, filename="3-reply.gpg", size=1234, uuid='test') draft_reply_4 = DraftReply(uuid='4', source=source, journalist=user, file_counter=3, timestamp=datetime.datetime(2000, 6, 6, 6, 0)) draft_reply_5 = DraftReply(uuid='5', source=source, journalist=user, file_counter=3, timestamp=datetime.datetime(2001, 6, 6, 6, 0)) reply_6 = Reply(source=source, journalist=user, filename="4-reply.gpg", size=1234, uuid='test2') draft_reply_7 = DraftReply(uuid='6', source=source, journalist=user, file_counter=4, timestamp=datetime.datetime(2002, 6, 6, 6, 0)) source.files = [file_1] source.messages = [message_2] source.replies = [reply_3, reply_6] source.draftreplies = [draft_reply_4, draft_reply_5, draft_reply_7] # Now these items should be in the source collection in the proper order assert source.collection[0] == file_1 assert source.collection[1] == message_2 assert source.collection[2] == reply_3 assert source.collection[3] == draft_reply_4 assert source.collection[4] == draft_reply_5 assert source.collection[5] == reply_6 assert source.collection[6] == draft_reply_7
def test_string_representation_of_draft_reply(): user = User(username='******') source = factory.Source() draft_reply = DraftReply(source=source, journalist=user, uuid='test') draft_reply.__str__() draft_reply.content = "hello" draft_reply.__str__()
def test_unstar_if_star(homedir, mocker, session, session_maker): """ Check if we call remove_star method if a source is stared. """ source = factory.Source() source.is_starred = True session.add(source) session.commit() api_client = mocker.MagicMock() api_client.remove_star = mocker.MagicMock() mock_sdk_source = mocker.Mock() mock_source_init = mocker.patch( "securedrop_client.logic.sdclientapi.Source", return_value=mock_sdk_source ) job = UpdateStarJob(source.uuid, source.is_starred) job.call_api(api_client, session) # ensure we call remove start wtih right source uuid mock_source_init.assert_called_once_with(uuid=source.uuid) api_client.remove_star.assert_called_once_with(mock_sdk_source)
def test_source_collection(): # Create some test submissions and replies source = factory.Source() file_ = File( source=source, uuid="test", size=123, filename="2-test.doc.gpg", download_url="http://test/test", ) message = Message( source=source, uuid="test", size=123, filename="3-test.doc.gpg", download_url="http://test/test", ) user = User(username="******") reply = Reply(source=source, journalist=user, filename="1-reply.gpg", size=1234, uuid="test") source.files = [file_] source.messages = [message] source.replies = [reply] # Now these items should be in the source collection in the proper order assert source.collection[0] == reply assert source.collection[1] == file_ assert source.collection[2] == message
def test_find_new_replies(mocker, session): source = factory.Source() reply_not_downloaded = factory.Reply(source=source, is_downloaded=False, is_decrypted=None, content=None) reply_decrypt_not_attempted = factory.Reply(source=source, is_downloaded=True, is_decrypted=None, content=None) reply_decrypt_failed = factory.Reply(source=source, is_downloaded=True, is_decrypted=False, content=None) reply_decrypt_success = factory.Reply(source=source, is_downloaded=True, is_decrypted=True, content="teehee") session.add(source) session.add(reply_decrypt_not_attempted) session.add(reply_not_downloaded) session.add(reply_decrypt_failed) session.add(reply_decrypt_success) session.commit() replies = find_new_replies(session) assert len(replies) == 3 for reply in replies: assert reply.is_downloaded is False or reply.is_decrypted is not True
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_send_reply_failure_unknown_error( homedir, mocker, session, session_maker, reply_status_codes ): """ Check that if the SendReplyJob api call fails when sending a message that SendReplyJobError is raised and the reply is not added to the local database. """ source = factory.Source() session.add(source) draft_reply = factory.DraftReply(uuid="mock_reply_uuid") session.add(draft_reply) session.commit() api_client = mocker.MagicMock() mocker.patch.object(api_client, "reply_source", side_effect=Exception) gpg = GpgHelper(homedir, session_maker, is_qubes=False) encrypt_fn = mocker.patch.object(gpg, "encrypt_to_source") job = SendReplyJob(source.uuid, "mock_reply_uuid", "mock_message", gpg) with pytest.raises(Exception): job.call_api(api_client, session) encrypt_fn.assert_called_once_with(source.uuid, "mock_message") replies = session.query(db.Reply).filter_by(uuid="mock_reply_uuid").all() assert len(replies) == 0 # Ensure that the draft reply is still in the db drafts = session.query(db.DraftReply).filter_by(uuid="mock_reply_uuid").all() assert len(drafts) == 1
def test_update_replies_missing_source(homedir, mocker, session): """ Verify that a reply to an invalid source is handled. """ data_dir = os.path.join(homedir, "data") journalist = factory.User(id=1) session.add(journalist) source = factory.Source() session.add(source) # Some remote reply objects from the API, one of which will exist in the # local database, the other will NOT exist in the local database # (this will be added to the database) remote_reply = make_remote_reply("nonexistent-source", journalist.uuid) remote_replies = [remote_reply] local_replies = [] error_logger = mocker.patch("securedrop_client.storage.logger.error") update_replies(remote_replies, local_replies, session, data_dir) error_logger.assert_called_once_with( f"No source found for reply {remote_reply.uuid}")
def test_delete_single_submission_or_reply_single_file_no_folder( homedir, mocker): """ This test checks that calling the delete_single_submission_or_reply method deletes the file even if its not in a per-document folder. """ source = factory.Source(journalist_designation="dissolved-steak") file_server_filename = "1-dissolved-steak-msg.gpg" test_obj = db.File( source=source, uuid="test", size=123, filename=file_server_filename, download_url="http://test/test", ) original_location = test_obj.location test_obj.location = mocker.MagicMock(side_effect=[ os.path.join(homedir, file_server_filename), original_location(homedir) ]) add_test_file_to_temp_dir(homedir, file_server_filename) delete_single_submission_or_reply_on_disk(test_obj, homedir) with pytest.raises(FileNotFoundError): open(os.path.join(homedir, file_server_filename), "r")
def test_delete_single_submission_or_reply_single_file(homedir, mocker): """ This test checks that calling the delete_single_submission_or_reply method deletes the file as well as the folder it's inside. """ source = factory.Source(journalist_designation="dissolved-steak") file_server_filename = "1-dissolved-steak-msg.gpg" test_obj = db.File( source=source, uuid="test", size=123, filename=file_server_filename, download_url="http://test/test", ) source_directory = os.path.dirname(test_obj.location(homedir)) add_test_file_to_temp_dir(source_directory, file_server_filename) delete_single_submission_or_reply_on_disk(test_obj, homedir) # Ensure both file and its containing folder are gone. with pytest.raises(FileNotFoundError): open(os.path.join(source_directory, file_server_filename), "r") with pytest.raises(FileNotFoundError): open(source_directory, "r")
def test_string_representation_of_submission(): source = factory.Source() submission = Submission(source=source, uuid="test", size=123, filename="test.docx", download_url='http://test/test') submission.__repr__()
def test_mark_reply_as_downloaded(mocker): session = mocker.MagicMock() reply = factory.Reply(source=factory.Source(), is_downloaded=False) session.query().filter_by().one.return_value = reply mark_as_downloaded(type(reply), "mock_uuid", session) assert reply.is_downloaded is True session.add.assert_called_once_with(reply) session.commit.assert_called_once_with()
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()